@atlisp/mcp 1.5.0 → 1.5.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.
Files changed (2) hide show
  1. package/dist/atlisp-mcp.js +1967 -1811
  2. package/package.json +2 -2
@@ -1,114 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/atlisp-mcp.js
4
- import path7 from "path";
5
- import fs6 from "fs";
4
+ import path8 from "path";
6
5
  import { fileURLToPath as fileURLToPath4 } from "url";
7
6
  import { createRequire } from "module";
8
- import os4 from "os";
9
- import express from "express";
10
- import rawBody from "raw-body";
11
- import iconv from "iconv-lite";
12
- import rateLimit from "express-rate-limit";
13
- import { randomUUID as randomUUID4 } from "crypto";
14
- import { AsyncLocalStorage } from "async_hooks";
15
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
7
+ import express2 from "express";
16
8
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
17
9
 
18
- // src/session-transport.js
19
- import { randomUUID } from "crypto";
20
- var SESSION_TIMEOUT = 30 * 60 * 1e3;
21
- var SessionTransport = class {
22
- constructor(sessionId) {
23
- this.sessionId = sessionId || randomUUID();
24
- this.onclose = null;
25
- this.onerror = null;
26
- this.onmessage = null;
27
- this._pendingRequest = null;
28
- this._started = false;
29
- this._closed = false;
30
- this._cleanupTimer = null;
31
- }
32
- async start() {
33
- if (this._started) {
34
- throw new Error("Transport already started");
35
- }
36
- this._started = true;
37
- }
38
- async send(message) {
39
- if (this._closed) return;
40
- if (this._pendingRequest) {
41
- const { resolve, reject, timeout } = this._pendingRequest;
42
- clearTimeout(timeout);
43
- this._pendingRequest = null;
44
- resolve(message);
45
- }
46
- }
47
- async close() {
48
- if (this._closed) return;
49
- this._closed = true;
50
- if (this._pendingRequest) {
51
- const { reject, timeout } = this._pendingRequest;
52
- clearTimeout(timeout);
53
- this._pendingRequest = null;
54
- reject(new Error("Transport closed"));
55
- }
56
- this._clearCleanupTimer();
57
- this.onclose?.();
58
- }
59
- async handleRequest(message, timeoutMs = 6e4) {
60
- if (this._closed) {
61
- throw new Error("Transport closed");
62
- }
63
- if (!message || !message.method) {
64
- throw new Error("Invalid message");
65
- }
66
- if (!message.id) {
67
- this.onmessage?.(message);
68
- return null;
69
- }
70
- return new Promise((resolve, reject) => {
71
- const timeout = setTimeout(() => {
72
- if (this._pendingRequest?.resolve === resolve) {
73
- this._pendingRequest = null;
74
- reject(new Error("Request timeout"));
75
- }
76
- }, timeoutMs);
77
- this._pendingRequest = { resolve, reject, timeout };
78
- this.onmessage?.(message);
79
- });
80
- }
81
- resetCleanupTimer() {
82
- this._clearCleanupTimer();
83
- this._cleanupTimer = setTimeout(() => {
84
- this.close();
85
- }, SESSION_TIMEOUT);
86
- }
87
- _clearCleanupTimer() {
88
- if (this._cleanupTimer) {
89
- clearTimeout(this._cleanupTimer);
90
- this._cleanupTimer = null;
91
- }
92
- }
93
- };
94
-
95
- // src/atlisp-mcp.js
96
- import {
97
- ListToolsRequestSchema,
98
- CallToolRequestSchema,
99
- ListResourcesRequestSchema,
100
- ReadResourceRequestSchema,
101
- SubscribeRequestSchema,
102
- UnsubscribeRequestSchema,
103
- ListPromptsRequestSchema,
104
- GetPromptRequestSchema
105
- } from "@modelcontextprotocol/sdk/types.js";
106
-
107
10
  // src/cad.js
108
11
  import { spawn } from "child_process";
109
12
  import path from "path";
110
13
  import { fileURLToPath } from "url";
111
- import { randomUUID as randomUUID2 } from "crypto";
14
+ import { randomUUID } from "crypto";
112
15
 
113
16
  // src/constants.js
114
17
  var CAD_PLATFORMS = ["AutoCAD", "ZWCAD", "GStarCAD", "BricsCAD"];
@@ -133,6 +36,27 @@ var MOCK_PACKAGES = [
133
36
  ];
134
37
 
135
38
  // src/config.js
39
+ import { z } from "zod";
40
+ var envSchema = z.object({
41
+ PORT: z.string().optional().default("8110"),
42
+ HOST: z.string().optional().default("0.0.0.0"),
43
+ TRANSPORT: z.enum(["http", "stdio"]).optional().default("http"),
44
+ MCP_API_KEY: z.string().optional().default(""),
45
+ ENABLE_SSE: z.string().optional().default("true"),
46
+ MESSAGE_TIMEOUT: z.string().optional().default("60000"),
47
+ HEARTBEAT_INTERVAL: z.string().optional().default("30000"),
48
+ BUSY_RETRIES: z.string().optional().default("10"),
49
+ BUSY_DELAY: z.string().optional().default("500"),
50
+ ENABLE_CORS: z.string().optional().default("true"),
51
+ CORS_ORIGIN: z.string().optional().default("*"),
52
+ RATE_LIMIT_WINDOW: z.string().optional().default("60000"),
53
+ RATE_LIMIT_MAX: z.string().optional().default("100"),
54
+ RESOURCE_CACHE_TTL: z.string().optional().default("5000"),
55
+ FUNCTION_LIB_CACHE_TTL: z.string().optional().default("300000"),
56
+ DEBUG: z.string().optional().default("false"),
57
+ DEBUG_FILE: z.string().optional().default(""),
58
+ FUNCTION_LIB_URL: z.string().optional().default("http://s3.atlisp.cn/json/functions.json")
59
+ });
136
60
  function parseArgs() {
137
61
  const args = process.argv.slice(2);
138
62
  const result = { transport: "http", port: "8110", host: "0.0.0.0" };
@@ -153,23 +77,31 @@ function parseArgs() {
153
77
  return result;
154
78
  }
155
79
  var cliArgs = parseArgs();
80
+ var envResult = envSchema.safeParse(process.env);
81
+ if (!envResult.success) {
82
+ console.error("\u73AF\u5883\u53D8\u91CF\u914D\u7F6E\u9519\u8BEF:", envResult.error.flatten().fieldErrors);
83
+ process.exit(1);
84
+ }
85
+ var env = envResult.data;
156
86
  var config = {
157
- port: parseInt(process.env.PORT || cliArgs.port, 10),
158
- host: process.env.HOST || cliArgs.host,
159
- transport: process.env.TRANSPORT || cliArgs.transport,
160
- apiKey: process.env.MCP_API_KEY || "",
161
- enableSse: process.env.ENABLE_SSE !== "false",
162
- messageTimeout: parseInt(process.env.MESSAGE_TIMEOUT || "60000", 10),
163
- heartbeatInterval: parseInt(process.env.HEARTBEAT_INTERVAL || "30000", 10),
164
- busyRetries: parseInt(process.env.BUSY_RETRIES || "10", 10),
165
- busyDelay: parseInt(process.env.BUSY_DELAY || "500", 10),
166
- enableCors: process.env.ENABLE_CORS !== "false",
167
- corsOrigin: process.env.CORS_ORIGIN || "*",
168
- rateLimitWindow: parseInt(process.env.RATE_LIMIT_WINDOW || "60000", 10),
169
- rateLimitMax: parseInt(process.env.RATE_LIMIT_MAX || "100", 10),
170
- resourceCacheTtl: parseInt(process.env.RESOURCE_CACHE_TTL || "5000", 10),
171
- debug: process.env.DEBUG === "true",
172
- debugFile: process.env.DEBUG_FILE || ""
87
+ port: parseInt(env.PORT || cliArgs.port, 10),
88
+ host: env.HOST || cliArgs.host,
89
+ transport: process.env.TRANSPORT && env.TRANSPORT ? env.TRANSPORT : cliArgs.transport,
90
+ apiKey: env.MCP_API_KEY,
91
+ enableSse: env.ENABLE_SSE === "true",
92
+ messageTimeout: parseInt(env.MESSAGE_TIMEOUT, 10),
93
+ heartbeatInterval: parseInt(env.HEARTBEAT_INTERVAL, 10),
94
+ busyRetries: parseInt(env.BUSY_RETRIES, 10),
95
+ busyDelay: parseInt(env.BUSY_DELAY, 10),
96
+ enableCors: env.ENABLE_CORS === "true",
97
+ corsOrigin: env.CORS_ORIGIN,
98
+ rateLimitWindow: parseInt(env.RATE_LIMIT_WINDOW, 10),
99
+ rateLimitMax: parseInt(env.RATE_LIMIT_MAX, 10),
100
+ resourceCacheTtl: parseInt(env.RESOURCE_CACHE_TTL, 10),
101
+ functionLibCacheTtl: parseInt(env.FUNCTION_LIB_CACHE_TTL, 10),
102
+ debug: env.DEBUG === "true",
103
+ debugFile: env.DEBUG_FILE,
104
+ functionLibUrl: env.FUNCTION_LIB_URL
173
105
  };
174
106
  var config_default = config;
175
107
 
@@ -308,7 +240,7 @@ function startHeartbeat() {
308
240
  }
309
241
  function sendMessage(msg) {
310
242
  const w = getWorker();
311
- const requestId = randomUUID2();
243
+ const requestId = randomUUID();
312
244
  return new Promise((resolve, reject) => {
313
245
  const timeout = setTimeout(() => {
314
246
  const pending = pendingRequests.get(requestId);
@@ -442,7 +374,7 @@ var cad = new CadConnection();
442
374
  import fs from "fs";
443
375
  import path2 from "path";
444
376
  import os from "os";
445
- var DEBUG_FILE = process.env.DEBUG_FILE || path2.join(os.tmpdir(), "mcp-server-debug.log");
377
+ var DEBUG_FILE = config_default.debugFile || path2.join(os.tmpdir(), "mcp-server-debug.log");
446
378
  var MAX_LOG_SIZE = 10 * 1024 * 1024;
447
379
  var MAX_LOG_FILES = 5;
448
380
  var stream = null;
@@ -476,10 +408,28 @@ function getStream() {
476
408
  }
477
409
  return stream;
478
410
  }
479
- function log(msg) {
480
- const entry = `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
481
- `;
482
- getStream().write(entry);
411
+ function formatMessage(level, message, meta = {}) {
412
+ const entry = {
413
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
414
+ level,
415
+ message,
416
+ ...meta,
417
+ pid: process.pid,
418
+ hostname: os.hostname()
419
+ };
420
+ return JSON.stringify(entry);
421
+ }
422
+ function log(message, meta = {}) {
423
+ getStream().write(formatMessage("INFO", message, meta) + "\n");
424
+ if (config_default.debug) {
425
+ console.log(`[INFO] ${message}`, meta);
426
+ }
427
+ }
428
+ function error(message, meta = {}) {
429
+ getStream().write(formatMessage("ERROR", message, meta) + "\n");
430
+ if (config_default.debug) {
431
+ console.error(`[ERROR] ${message}`, meta);
432
+ }
483
433
  }
484
434
  function closeLog() {
485
435
  if (stream) {
@@ -488,1076 +438,1302 @@ function closeLog() {
488
438
  }
489
439
  }
490
440
 
491
- // src/handlers/cad-handlers.js
492
- async function connectCad(platform = null) {
493
- log(`connect_cad called${platform ? ` (target: ${platform})` : ""}, cad.connected before: ${cad.connected}`);
494
- try {
495
- log("calling cad.connect()...");
496
- const connected = await cad.connect(platform);
497
- log("cad.connect() returned: " + connected + ", cad.connected: " + cad.connected);
498
- if (connected) {
499
- return { content: [{ type: "text", text: `\u5DF2\u8FDE\u63A5\u5230 ${cad.getPlatform()} ${cad.getVersion()}` }] };
500
- }
501
- } catch (e) {
502
- log("connect_cad error: " + e.message);
441
+ // src/handlers/resource-handlers.js
442
+ import fs2 from "fs";
443
+ import path3 from "path";
444
+ import { fileURLToPath as fileURLToPath2 } from "url";
445
+ var __dirname2 = path3.dirname(fileURLToPath2(import.meta.url));
446
+ var STANDARDS_DIR = path3.join(__dirname2, "..", "standards");
447
+ var CACHE_TTL = config_default.resourceCacheTtl;
448
+ var resourceCache = /* @__PURE__ */ new Map();
449
+ function getCached(key) {
450
+ const entry = resourceCache.get(key);
451
+ if (entry && Date.now() - entry.timestamp < CACHE_TTL) {
452
+ return entry.data;
503
453
  }
504
- return { content: [{ type: "text", text: "\u672A\u80FD\u8FDE\u63A5\u6216\u542F\u52A8 CAD\uFF0C\u8BF7\u786E\u4FDD CAD \u5DF2\u5B89\u88C5" }], isError: true };
454
+ resourceCache.delete(key);
455
+ return null;
505
456
  }
506
- async function evalLisp(code, withResult = false, encoding = null) {
507
- if (!cad.connected) {
508
- await cad.connect();
509
- }
510
- if (!cad.connected) {
511
- return { content: [{ type: "text", text: "\u672A\u8FDE\u63A5 CAD" }], isError: true };
512
- }
513
- const trimmed = (code || "").trim();
514
- if (!trimmed) return { content: [{ type: "text", text: "\u547D\u4EE4\u4E3A\u7A7A\uFF0C\u672A\u53D1\u9001" }] };
515
- try {
516
- if (withResult) {
517
- const result = await cad.sendCommandWithResult(trimmed, encoding);
518
- if (result !== null) {
519
- return { content: [{ type: "text", text: result }] };
457
+ function setCache(key, data) {
458
+ resourceCache.set(key, { data, timestamp: Date.now() });
459
+ }
460
+ function clearCache(uri) {
461
+ if (uri) {
462
+ for (const key of resourceCache.keys()) {
463
+ if (key.startsWith(uri.split("://")[1]?.split("/")[0] + ":")) {
464
+ resourceCache.delete(key);
520
465
  }
521
- return { content: [{ type: "text", text: "\u6267\u884C\u5931\u8D25\u6216\u65E0\u8FD4\u56DE\u503C" }], isError: true };
522
- } else {
523
- await cad.sendCommand(trimmed + "\n");
524
- return { content: [{ type: "text", text: `\u5DF2\u53D1\u9001\u547D\u4EE4` }] };
525
466
  }
526
- } catch (e) {
527
- return { content: [{ type: "text", text: `\u9519\u8BEF: ${e.message}` }], isError: true };
467
+ } else {
468
+ resourceCache.clear();
528
469
  }
529
470
  }
530
- async function getCadInfo() {
531
- if (!cad.connected) {
532
- await cad.connect();
471
+ function parseLispList(str) {
472
+ if (!str || typeof str !== "string") return null;
473
+ const trimmed = str.trim();
474
+ if (!trimmed.startsWith("(") || !trimmed.endsWith(")")) return null;
475
+ const content = trimmed.slice(1, -1).trim();
476
+ if (!content) return [];
477
+ const result = [];
478
+ let depth = 0;
479
+ let current = "";
480
+ let inString = false;
481
+ let escaped = false;
482
+ for (let i = 0; i < content.length; i++) {
483
+ const ch = content[i];
484
+ if (escaped) {
485
+ current += ch;
486
+ escaped = false;
487
+ continue;
488
+ }
489
+ if (ch === "\\") {
490
+ current += ch;
491
+ escaped = true;
492
+ continue;
493
+ }
494
+ if (ch === '"') {
495
+ current += ch;
496
+ inString = !inString;
497
+ continue;
498
+ }
499
+ if (inString) {
500
+ current += ch;
501
+ continue;
502
+ }
503
+ if (ch === "(" || ch === "[") {
504
+ current += ch;
505
+ depth++;
506
+ } else if (ch === ")" || ch === "]") {
507
+ current += ch;
508
+ depth--;
509
+ } else if (ch === " " && depth === 0) {
510
+ if (current.trim()) {
511
+ result.push(current.trim());
512
+ current = "";
513
+ }
514
+ continue;
515
+ }
516
+ current += ch;
533
517
  }
534
- if (!cad.connected) return { content: [{ type: "text", text: "CAD \u672A\u8FDE\u63A5" }] };
535
- const info = await cad.getInfo();
536
- return { content: [{ type: "text", text: info }] };
537
- }
538
- async function atCommand(command) {
539
- if (!cad.connected) return { content: [{ type: "text", text: "\u8BF7\u5148\u8FDE\u63A5 CAD" }], isError: true };
540
- const trimmed = (command || "").trim();
541
- if (!trimmed) return { content: [{ type: "text", text: "\u547D\u4EE4\u4E3A\u7A7A\uFF0C\u672A\u53D1\u9001" }] };
542
- await cad.sendCommand(trimmed + "\n");
543
- return { content: [{ type: "text", text: "\u5DF2\u53D1\u9001\u547D\u4EE4" }] };
518
+ if (current.trim()) result.push(current.trim());
519
+ return result.map((item) => {
520
+ const t = item.trim();
521
+ if (t === "nil") return null;
522
+ if (t === "t") return true;
523
+ if (t === "T") return true;
524
+ const num = Number(t);
525
+ if (!isNaN(num) && t !== "") return num;
526
+ if (t.startsWith('"') && t.endsWith('"')) return t.slice(1, -1);
527
+ return t;
528
+ });
544
529
  }
545
- async function newDocument() {
546
- if (!cad.connected) {
547
- await cad.connect();
530
+ function parseLispRecursive(str) {
531
+ if (!str || typeof str !== "string") return null;
532
+ str = str.trim();
533
+ if (!str) return null;
534
+ let pos = 0;
535
+ function skipWs() {
536
+ while (pos < str.length && /\s/.test(str[pos])) pos++;
548
537
  }
549
- if (!cad.connected) return { content: [{ type: "text", text: "\u672A\u8FDE\u63A5 CAD" }], isError: true };
550
- const result = await cad.newDoc();
551
- if (result) {
552
- return { content: [{ type: "text", text: "\u5DF2\u5728 CAD \u4E2D\u65B0\u5EFA\u7A7A\u767D\u6587\u6863" }] };
538
+ function parseValue() {
539
+ skipWs();
540
+ if (pos >= str.length) return null;
541
+ const ch = str[pos];
542
+ if (ch === "(") return parseList();
543
+ if (ch === '"') return parseString();
544
+ if (ch === "'") {
545
+ pos++;
546
+ return parseValue();
547
+ }
548
+ return parseAtom();
553
549
  }
554
- return { content: [{ type: "text", text: "\u65B0\u5EFA\u6587\u6863\u5931\u8D25" }], isError: true };
555
- }
556
- async function bringToFront() {
557
- if (!cad.connected) {
558
- await cad.connect();
559
- }
560
- if (!cad.connected) return { content: [{ type: "text", text: "\u672A\u8FDE\u63A5 CAD" }], isError: true };
561
- const result = await cad.bringToFront();
562
- if (result) {
563
- return { content: [{ type: "text", text: "\u5DF2\u5C06 CAD \u7A97\u53E3\u5207\u6362\u5230\u524D\u53F0" }] };
564
- }
565
- return { content: [{ type: "text", text: "\u5207\u6362\u524D\u53F0\u5931\u8D25" }], isError: true };
566
- }
567
- async function installAtlisp() {
568
- if (!cad.connected) {
569
- await cad.connect();
570
- }
571
- if (!cad.connected) return { content: [{ type: "text", text: "\u672A\u8FDE\u63A5 CAD" }], isError: true };
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))))`;
573
- await cad.sendCommand(installCode + "\n");
574
- return { content: [{ type: "text", text: "\u5DF2\u53D1\u9001 @lisp \u5B89\u88C5\u4EE3\u7801\u5230 CAD" }] };
575
- }
576
- async function listFunctionsInCad() {
577
- if (!cad.connected) {
578
- await cad.connect();
579
- }
580
- if (!cad.connected) return { content: [{ type: "text", text: "\u672A\u8FDE\u63A5 CAD" }], isError: true };
581
- try {
582
- const result = await cad.sendCommandWithResult("(atoms-family 0)", null);
583
- if (result) return { content: [{ type: "text", text: result }] };
584
- return { content: [{ type: "text", text: "CAD \u51FD\u6570\u5217\u8868\u4E3A\u7A7A" }] };
585
- } catch (e) {
586
- return { content: [{ type: "text", text: `\u9519\u8BEF: ${e.message}` }], isError: true };
550
+ function parseString() {
551
+ pos++;
552
+ let r = "";
553
+ while (pos < str.length && str[pos] !== '"') {
554
+ if (str[pos] === "\\" && pos + 1 < str.length) {
555
+ pos++;
556
+ if (str[pos] === '"') r += '"';
557
+ else if (str[pos] === "n") r += "\n";
558
+ else if (str[pos] === "t") r += " ";
559
+ else if (str[pos] === "\\") r += "\\";
560
+ else r += str[pos];
561
+ } else {
562
+ r += str[pos];
563
+ }
564
+ pos++;
565
+ }
566
+ pos++;
567
+ return r;
587
568
  }
588
- }
589
- async function getSystemStatus() {
590
- const status = {
591
- mcp: { version: process.env.npm_package_version || "unknown" },
592
- worker: { alive: false },
593
- cad: { connected: false, platform: null, version: null, busy: false, hasDoc: false },
594
- lisp: { available: false, testResult: null, error: null },
595
- packages: null
596
- };
597
- try {
598
- status.worker.alive = await cad.ping();
599
- } catch {
600
- status.worker.alive = false;
569
+ function parseAtom() {
570
+ const start = pos;
571
+ while (pos < str.length && !/\s/.test(str[pos]) && str[pos] !== ")" && str[pos] !== "(") pos++;
572
+ const atom = str.slice(start, pos);
573
+ if (atom === "nil" || atom === "NIL") return null;
574
+ if (atom === "t" || atom === "T") return true;
575
+ const num = Number(atom);
576
+ if (!isNaN(num) && atom !== "") return num;
577
+ return atom;
601
578
  }
602
- status.cad.connected = cad.connected;
603
- if (cad.connected) {
604
- status.cad.platform = cad.getPlatform();
605
- status.cad.version = cad.getVersion();
606
- try {
607
- status.cad.busy = await cad.isBusy();
608
- } catch {
609
- }
610
- try {
611
- const doc = await cad.hasDoc();
612
- status.cad.hasDoc = doc.hasDoc;
613
- } catch {
614
- }
615
- try {
616
- const r = await cad.sendCommandWithResult("(+ 1 2)");
617
- if (r !== null && r !== void 0) {
618
- status.lisp.available = true;
619
- status.lisp.testResult = String(r).trim();
579
+ function parseList() {
580
+ pos++;
581
+ const items = [];
582
+ while (pos < str.length) {
583
+ skipWs();
584
+ if (pos >= str.length) break;
585
+ if (str[pos] === ")") {
586
+ pos++;
587
+ return items;
620
588
  }
621
- } catch (e) {
622
- status.lisp.error = e.message;
623
- }
624
- try {
625
- const pkgResult = await cad.sendCommandWithResult("(@::package-list-in-local)");
626
- if (pkgResult && pkgResult !== "nil") {
627
- status.packages = pkgResult;
589
+ const left = parseValue();
590
+ if (left === void 0) break;
591
+ skipWs();
592
+ if (str[pos] === "." && (pos + 1 >= str.length || /\s/.test(str[pos + 1]) || str[pos + 1] === ")")) {
593
+ pos++;
594
+ skipWs();
595
+ if (pos < str.length && str[pos] !== ")") {
596
+ const right = parseValue();
597
+ items.push([left, right]);
598
+ skipWs();
599
+ } else {
600
+ items.push(left);
601
+ }
602
+ } else {
603
+ items.push(left);
628
604
  }
629
- } catch {
630
605
  }
606
+ return items;
631
607
  }
632
- return { content: [{ type: "text", text: JSON.stringify(status, null, 2) }] };
633
- }
634
- async function initAtlisp() {
635
- if (!cad.connected) {
636
- await cad.connect();
637
- }
638
- if (!cad.connected) return { content: [{ type: "text", text: "\u672A\u8FDE\u63A5 CAD" }], isError: true };
639
- const code = `(if (null @::load-module)
640
- (progn
641
- (vl-load-com)
642
- (setq s strcat h"http"o(vlax-create-object (s"win"h".win"h"request.5.1"))
643
- v vlax-invoke e eval r read)(v o'open "get" (s h"://""atlisp.""cn/cloud"):vlax-true)
644
- (v o'send)
645
- (v o'WaitforResponse 1000)
646
- (e(r(vlax-get o'ResponseText)))))`;
647
- try {
648
- await cad.sendCommand(code + "\n");
649
- return { content: [{ type: "text", text: "\u5DF2\u53D1\u9001 @lisp \u51FD\u6570\u5E93\u52A0\u8F7D\u4EE3\u7801\u5230 CAD" }] };
650
- } catch (e) {
651
- return { content: [{ type: "text", text: `\u9519\u8BEF: ${e.message}` }], isError: true };
652
- }
608
+ return parseValue();
653
609
  }
654
-
655
- // src/handlers/package-handlers.js
656
- var PACKAGES_LIST_URL = process.env.PACKAGES_LIST_URL || "http://s3.atlisp.cn/packages-list?fmt=json";
657
- var cachedPackages = null;
658
- var cachedAt = null;
659
- var CACHE_TTL = 10 * 60 * 1e3;
660
- async function fetchPackages() {
661
- const now = Date.now();
662
- if (cachedPackages && cachedAt && now - cachedAt < CACHE_TTL) {
663
- return cachedPackages;
664
- }
665
- try {
666
- const response = await fetch(PACKAGES_LIST_URL, { headers: { "User-Agent": "atlisp-mcp" } });
667
- if (response.ok) {
668
- const data = await response.json();
669
- cachedPackages = data;
670
- cachedAt = now;
671
- return data;
610
+ function lispPairsToObject(pairs) {
611
+ if (!Array.isArray(pairs)) return pairs;
612
+ const obj = {};
613
+ for (let i = 0; i < pairs.length - 1; i += 2) {
614
+ const key = pairs[i];
615
+ const val = pairs[i + 1];
616
+ if (typeof key === "string" || typeof key === "number") {
617
+ obj[String(key)] = Array.isArray(val) ? lispPairsToObject(val) : val;
672
618
  }
673
- } catch (e) {
674
- }
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 [];
681
- }
682
- async function listPackages() {
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
619
  }
690
- const result = await cad.sendCommandWithResult("(@::package-list-in-local)");
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");
696
- return { content: [{ type: "text", text: `\u5DF2\u5B89\u88C5\u7684 @lisp \u5305:
697
- ${text}` }] };
620
+ return obj;
698
621
  }
699
- async function searchPackages(query) {
700
- const data = await fetchPackages();
701
- if (!data) {
702
- return { content: [{ type: "text", text: "\u65E0\u6CD5\u83B7\u53D6\u5305\u5217\u8868" }], isError: true };
622
+ function parseEntity(arr) {
623
+ if (!Array.isArray(arr) || arr.length < 5) return null;
624
+ const [type, handle, layer, color, linetype, ...rest] = arr;
625
+ const entity = {
626
+ type: String(type).toUpperCase().replace(/^"|"$/g, ""),
627
+ handle: String(handle),
628
+ layer: String(layer),
629
+ color,
630
+ linetype: String(linetype)
631
+ };
632
+ const entType = typeof entity.type === "string" ? entity.type.toUpperCase() : entity.type;
633
+ if (/^(TEXT|MTEXT|ATTRIB|TCH_TEXT)$/.test(entType) && rest[0] != null) {
634
+ entity.text = String(rest[0]);
703
635
  }
704
- const items = flattenPackages(data);
705
- const results = [];
706
- if (query) {
707
- const q = query.toLowerCase();
708
- for (const p of items) {
709
- const name = (p.name || "").toLowerCase();
710
- const desc = (p.description || "").toLowerCase();
711
- if (name.includes(q) || desc.includes(q)) results.push(p);
712
- }
713
- } else {
714
- for (const p of items) results.push(p);
636
+ if (/^(LINE|LWPOLYLINE|POLYLINE|ARC|CIRCLE|SPLINE|ELLIPSE|XLINE|RAY)$/.test(entType) && Array.isArray(rest[0])) {
637
+ entity.points = rest[0].map((pt) => {
638
+ if (Array.isArray(pt) && pt.length >= 2)
639
+ return { x: Number(pt[0]), y: Number(pt[1]), z: Number(pt[2] || 0) };
640
+ return null;
641
+ }).filter(Boolean);
715
642
  }
716
- if (results.length === 0) {
717
- return { content: [{ type: "text", text: `\u672A\u627E\u5230\u5305\u542B "${query}" \u7684\u5305` }] };
643
+ if (entType === "INSERT" && rest[0] != null) {
644
+ entity.blockName = String(rest[0]);
718
645
  }
719
- return { content: [{ type: "text", text: `\u641C\u7D22\u7ED3\u679C (${results.length}):
720
- ${results.map((p) => `${p.name} v${p.version || ""} - ${p.description || ""}`).join("\n")}` }] };
646
+ return entity;
721
647
  }
722
- async function installPackage(packageName) {
723
- const data = await fetchPackages();
724
- const items = flattenPackages(data);
725
- const pkg = items.find((p) => p.name === packageName);
726
- if (!pkg) {
727
- return { content: [{ type: "text", text: `\u9519\u8BEF: \u5305 "${packageName}" \u4E0D\u5B58\u5728` }], isError: true };
648
+ function buildEntityCode({ type, layer, bbox }) {
649
+ const items = [];
650
+ if (type) items.push(`(cons 0 "${type.replace(/"/g, '\\"')}")`);
651
+ if (layer) items.push(`(cons 8 "${layer.replace(/"/g, '\\"')}")`);
652
+ if (bbox) {
653
+ const parts = bbox.split(",").map((s) => parseFloat(s.trim()));
654
+ if (parts.length === 4) {
655
+ const [x1, y1, x2, y2] = parts;
656
+ items.push(
657
+ `'(-4 . "<AND")`,
658
+ `'(-4 . ">=,>=,*") (list 10 ${x1} ${y1} 0.0)`,
659
+ `'(-4 . "<=,<=,*") (list 10 ${x2} ${y2} 0.0)`,
660
+ `'(-4 . "AND>")`
661
+ );
662
+ }
728
663
  }
729
- if (cad.connected) {
730
- await cad.sendCommand(`(@::load-module '${packageName})
731
- `);
732
- }
733
- return { content: [{ type: "text", text: `\u5305 "${packageName}" \u5B89\u88C5\u547D\u4EE4\u5DF2\u53D1\u9001\uFF01` }] };
664
+ const filterExpr = items.length ? `(list ${items.join(" ")})` : "nil";
665
+ const ssgetExpr = items.length ? `(ssget "_X" ${filterExpr})` : '(ssget "_X")';
666
+ return `(progn
667
+ (vl-load-com)
668
+ (defun @e:safe-fn (f ent / r)
669
+ (setq r (vl-catch-all-apply f (list ent)))
670
+ (if (vl-catch-all-error-p r) nil r))
671
+ (defun @e:dp (ent / ed typ gcl is)
672
+ (setq ed (entget ent) typ (cdr (assoc 0 ed)) gcl (assoc 62 ed) is (assoc 6 ed))
673
+ (append
674
+ (list typ (cdr (assoc 5 ed)) (cdr (assoc 8 ed))
675
+ (if gcl (cdr gcl) 256)
676
+ (if is (cdr is) "ByLayer"))
677
+ (cond
678
+ ((wcmatch typ "TEXT,MTEXT,ATTRIB,TCH_TEXT")
679
+ (list (@e:safe-fn '(lambda (e) (text:remove-fmt (text:get-mtext e))) ent)))
680
+ ((wcmatch typ "LINE,LWPOLYLINE,POLYLINE,ARC,CIRCLE,SPLINE,ELLIPSE,XLINE,RAY")
681
+ (list (@e:safe-fn 'curve:get-points ent)))
682
+ ((= typ "INSERT")
683
+ (list (@e:safe-fn 'block:get-effectivename ent)))
684
+ (t nil))))
685
+ (setq @e:total (if (setq @e:ss ${ssgetExpr}) (sslength @e:ss) 0)
686
+ @e:ents nil @e:i 0)
687
+ (while (and @e:ss (< @e:i @e:total)(< (length @e:ents) 3000))
688
+ (setq @e:ents (cons (@e:dp (ssname @e:ss @e:i)) @e:ents) @e:i (1+ @e:i)))
689
+ (vl-prin1-to-string (list @e:total (reverse @e:ents))))`;
734
690
  }
735
-
736
- // src/handlers/function-handlers.js
737
- import fs2 from "fs";
738
- import path3 from "path";
739
- import os2 from "os";
740
- var FUNCTIONS_DIR = process.env.FUNCTIONS_DIR || "";
741
- var FUNCTIONS_URL = process.env.FUNCTIONS_URL || "http://s3.atlisp.cn/json/functions.json";
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() {
746
- try {
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);
691
+ function buildTextCode({ layer, bbox, offset, limit }) {
692
+ const items = [`'(0 . "TEXT,MTEXT,ATTRIB,TCH_TEXT")`];
693
+ if (layer) items.push(`(cons 8 "${layer.replace(/"/g, '\\"')}")`);
694
+ if (bbox) {
695
+ const parts = bbox.split(",").map((s) => parseFloat(s.trim()));
696
+ if (parts.length === 4) {
697
+ const [x1, y1, x2, y2] = parts;
698
+ items.push(
699
+ `'(-4 . "<AND")`,
700
+ `'(-4 . ">=,>=,*") (list 10 ${x1} ${y1} 0.0)`,
701
+ `'(-4 . "<=,<=,*") (list 10 ${x2} ${y2} 0.0)`,
702
+ `'(-4 . "AND>")`
703
+ );
751
704
  }
752
- } catch {
753
705
  }
754
- return null;
706
+ const filterExpr = `(list ${items.join(" ")})`;
707
+ return `(progn
708
+ (defun @t:props (ent / ed typ result item ins)
709
+ (setq ed (entget ent) typ (cdr (assoc 0 ed)))
710
+ (setq result (list (vl-prin1-to-string typ) (vl-prin1-to-string (cdr (assoc 5 ed))) (vl-prin1-to-string (cdr (assoc 8 ed)))
711
+ (if (setq item (assoc 62 ed)) (cdr item) "ByLayer")
712
+ (vl-prin1-to-string (if (setq item (assoc 6 ed)) (cdr item) "ByLayer"))))
713
+ (setq ins (cdr (assoc 10 ed)))
714
+ (append result (list (car ins) (cadr ins) (caddr ins)
715
+ (vl-prin1-to-string (cdr (assoc 1 ed))) (cdr (assoc 40 ed)) (cdr (assoc 50 ed))
716
+ (vl-prin1-to-string (if (setq item (assoc 7 ed)) (cdr item) "Standard")))))
717
+ (defun @t:run nil
718
+ (setq ss (ssget "_X" ${filterExpr}))
719
+ (if (null ss)
720
+ (list "total" 0 "offset" ${offset} "limit" ${limit})
721
+ (progn
722
+ (setq total (sslength ss) texts nil i ${offset})
723
+ (while (and (< i total) (< (length texts) ${limit}))
724
+ (setq texts (append texts (list (@t:props (ssname ss i)))) i (1+ i)))
725
+ (list "total" total "offset" ${offset} "limit" ${limit} "texts" texts))))
726
+ (@t:run))
727
+ `;
755
728
  }
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}`);
729
+ var RESOURCES = [
730
+ { uri: "atlisp://cad/info", name: "CAD Info", description: "CAD \u8FDE\u63A5\u4FE1\u606F", mimeType: "application/json", subscribable: true },
731
+ { uri: "atlisp://dwg/layers", name: "Layers", description: "\u56FE\u5C42\u5217\u8868", mimeType: "application/json", subscribable: true },
732
+ { uri: "atlisp://dwg/entities", name: "Entities", description: "\u56FE\u5143\u5217\u8868\uFF0C\u8FD4\u56DE\u603B\u6570\u548C\u524D3000\u4E2A\u56FE\u5143\uFF0C\u6587\u672C\u9644\u52A0 text \u5B57\u6BB5\uFF0C\u66F2\u7EBF\u9644\u52A0 points \u6570\u7EC4\uFF0CINSERT \u9644\u52A0 blockName\uFF0C\u652F\u6301 ?type=LINE&layer=0&bbox=0,0,100,100 \u8FC7\u6EE4", mimeType: "application/json", subscribable: true },
733
+ { 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=5000&offset=0", mimeType: "application/json", subscribable: true },
734
+ { uri: "atlisp://dwg/name", name: "DWG Name", description: "\u5F53\u524D DWG \u6587\u4EF6\u540D", mimeType: "application/json", subscribable: true },
735
+ { uri: "atlisp://dwg/path", name: "DWG Path", description: "\u6587\u4EF6\u5B8C\u6574\u8DEF\u5F84", mimeType: "application/json", subscribable: true },
736
+ { uri: "atlisp://cad/dwgs", name: "DWG List", description: "\u6240\u6709\u6253\u5F00\u7684 DWG \u6587\u4EF6\u5217\u8868", mimeType: "application/json", subscribable: true },
737
+ { uri: "atlisp://packages", name: "Packages", description: "\u5DF2\u5B89\u88C5\u5305\u5217\u8868", mimeType: "application/json", subscribable: true },
738
+ { uri: "atlisp://platforms", name: "Platforms", description: "\u652F\u6301\u7684\u5E73\u53F0\u4FE1\u606F", mimeType: "application/json", subscribable: false },
739
+ { uri: "atlisp://standards/drafting", name: "Drafting Standards", description: "CAD \u5236\u56FE\u89C4\u8303", mimeType: "application/json", subscribable: false },
740
+ { uri: "atlisp://standards/coding", name: "Coding Standards", description: "@lisp \u7F16\u7801\u89C4\u8303", mimeType: "application/json", subscribable: false }
741
+ ];
742
+ function parseQuery(uri) {
743
+ const qIdx = uri.indexOf("?");
744
+ if (qIdx === -1) return { base: uri, params: {} };
745
+ const base = uri.substring(0, qIdx);
746
+ const params = {};
747
+ uri.substring(qIdx + 1).split("&").forEach((p) => {
748
+ const eqIdx = p.indexOf("=");
749
+ const k = eqIdx === -1 ? p : p.substring(0, eqIdx);
750
+ const v = eqIdx === -1 ? "" : p.substring(eqIdx + 1);
751
+ if (k) params[k] = decodeURIComponent(v);
752
+ });
753
+ return { base, params };
754
+ }
755
+ async function listResources(subscribedUris = []) {
756
+ return RESOURCES.map((r) => ({
757
+ uri: r.uri,
758
+ name: r.name,
759
+ description: r.description,
760
+ mimeType: r.mimeType,
761
+ subscribed: subscribedUris.includes(r.uri)
762
+ }));
763
+ }
764
+ async function readResource(uri) {
765
+ const { base, params } = parseQuery(uri);
766
+ switch (base) {
767
+ case "atlisp://cad/info":
768
+ return await getCadInfo();
769
+ case "atlisp://dwg/layers":
770
+ return await getLayers(params.name);
771
+ case "atlisp://dwg/entities":
772
+ return await getEntities(params);
773
+ case "atlisp://dwg/texts":
774
+ return await getTexts(params);
775
+ case "atlisp://dwg/name":
776
+ return await getDwgName();
777
+ case "atlisp://dwg/path":
778
+ return await getDwgPath();
779
+ case "atlisp://cad/dwgs":
780
+ return await getDwgList();
781
+ case "atlisp://packages":
782
+ return await getPackages(params.name);
783
+ case "atlisp://platforms":
784
+ return getPlatforms();
785
+ case "atlisp://standards/drafting":
786
+ return getDraftingStandards();
787
+ case "atlisp://standards/coding":
788
+ return getCodingStandards();
789
+ default:
790
+ throw new Error(`\u8D44\u6E90\u4E0D\u5B58\u5728: ${uri}`);
762
791
  }
763
792
  }
764
- async function fetchFunctions() {
765
- const diskCache = readCacheFromDisk();
766
- if (diskCache) return diskCache;
793
+ async function getCadInfo() {
794
+ const cached = getCached("cad:info");
795
+ if (cached) return cached;
796
+ if (!cad.connected) {
797
+ await cad.connect();
798
+ }
799
+ if (!cad.connected) {
800
+ const result2 = { connected: false, platform: null, version: null, busy: false };
801
+ setCache("cad:info", result2);
802
+ return result2;
803
+ }
804
+ let busy = false;
767
805
  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
- }
806
+ busy = await cad.isBusy();
774
807
  } catch (e) {
775
- log(`function-handlers fetch error: ${e.message}`);
808
+ log(`Error checking CAD busy status: ${e.message}`);
776
809
  }
777
- return diskCache;
810
+ const result = {
811
+ connected: true,
812
+ platform: cad.getPlatform(),
813
+ version: cad.getVersion(),
814
+ busy
815
+ };
816
+ setCache("cad:info", result);
817
+ return result;
778
818
  }
779
- async function getFunctionUsage(funcName, packageName) {
819
+ async function getLayers(filterName = null) {
820
+ const cacheKey = filterName ? `dwg:layers:${filterName}` : "dwg:layers:all";
821
+ const cached = getCached(cacheKey);
822
+ if (cached) return cached;
823
+ if (!cad.connected) await cad.connect();
824
+ if (!cad.connected) return [];
780
825
  try {
781
- let results = [];
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);
826
+ const code = filterName ? `(vl-prin1-to-string (cdr (assoc 2 (tblnext "LAYER" T))))` : `(progn (setq res nil)(while (setq e (tblnext "LAYER" (null res)))(setq res (cons (list (vl-prin1-to-string (cdr (assoc 2 e))) (cdr (assoc 62 e)) (cdr (assoc 70 e))) res))) (reverse res))`;
827
+ const result = await cad.sendCommandWithResult(code);
828
+ if (!result || result === "" || result === "nil") {
829
+ setCache(cacheKey, []);
830
+ return [];
787
831
  }
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 {
832
+ let layers = [];
833
+ try {
834
+ layers = JSON.parse(result);
835
+ } catch (e) {
836
+ const parsed = parseLispList(result);
837
+ if (parsed) {
838
+ layers = parsed;
839
+ } else {
840
+ layers = [];
798
841
  }
799
842
  }
800
- if (results.length === 0) {
801
- return { content: [{ type: "text", text: `\u672A\u627E\u5230\u51FD\u6570: ${funcName}` }], isError: true };
843
+ if (!Array.isArray(layers)) {
844
+ if (typeof layers === "number") {
845
+ layers = [[result, layers, 0]];
846
+ } else if (typeof layers === "string") {
847
+ layers = [[layers, 7, 0]];
848
+ }
802
849
  }
803
- const f = results[0];
804
- const text = `\u51FD\u6570: ${f.name}
805
- \u63CF\u8FF0: ${f.description || "-"}
806
- \u53C2\u6570: ${f.params?.join(", ") || "-"}
807
- \u8FD4\u56DE\u503C: ${f.return || "-"}
808
- \u793A\u4F8B: ${f.usage || "-"}`;
809
- return { content: [{ type: "text", text }] };
850
+ const mapped = layers.map((l) => {
851
+ const name = Array.isArray(l) ? l[0] : l;
852
+ const color = Array.isArray(l) && l[1] !== void 0 ? l[1] : 7;
853
+ const flags = Array.isArray(l) && l[2] !== void 0 ? l[2] : 0;
854
+ return {
855
+ name: String(name),
856
+ color,
857
+ locked: (flags & 4) !== 0,
858
+ frozen: (flags & 1) !== 0
859
+ };
860
+ });
861
+ setCache(cacheKey, mapped);
862
+ return mapped;
810
863
  } catch (e) {
811
- return { content: [{ type: "text", text: `\u9519\u8BEF: ${e.message}` }], isError: true };
864
+ setCache(cacheKey, []);
865
+ return [];
812
866
  }
813
867
  }
814
- async function listSymbols(packageName) {
868
+ async function getEntities(params = {}) {
869
+ const cacheKey = `dwg:entities:${JSON.stringify(params)}`;
870
+ const cached = getCached(cacheKey);
871
+ if (cached) return cached;
872
+ if (!cad.connected) await cad.connect();
873
+ if (!cad.connected) {
874
+ const result = { total: 0, entities: [] };
875
+ setCache(cacheKey, result);
876
+ return result;
877
+ }
815
878
  try {
816
- let allFuncs = [];
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 || "-"}`);
824
- }
825
- }
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 || {});
833
- if (packageName) {
834
- allFuncs = funcs.filter((f) => f.name.startsWith(`${packageName}:`));
835
- } else {
836
- for (const f of funcs) {
837
- allFuncs.push(`${f.name}: ${f.description || "-"}`);
838
- }
839
- }
840
- }
841
- } catch (err) {
842
- log(`listSymbols readDir error: ${err.message}`);
843
- }
844
- }
845
- if (allFuncs.length === 0) {
846
- return await listFunctionsInCad();
879
+ const code = buildEntityCode({
880
+ type: params.type || null,
881
+ layer: params.layer || null,
882
+ bbox: params.bbox || null
883
+ });
884
+ const resultStr = await cad.sendCommandWithResult(code);
885
+ if (!resultStr || resultStr === "" || resultStr === "nil") {
886
+ const result2 = { total: 0, entities: [] };
887
+ setCache(cacheKey, result2);
888
+ return result2;
847
889
  }
848
- if (packageName) {
849
- return { content: [{ type: "text", text: `${packageName} \u5305\u51FD\u6570 (${allFuncs.length}):
850
- ${allFuncs.map((f) => f.name).join("\n")}` }] };
890
+ let total = 0;
891
+ let entities = [];
892
+ const parsed = parseLispRecursive(resultStr);
893
+ if (parsed && Array.isArray(parsed) && parsed.length >= 2) {
894
+ total = typeof parsed[0] === "number" ? parsed[0] : 0;
895
+ const rawEntities = Array.isArray(parsed[1]) ? parsed[1] : [];
896
+ entities = rawEntities.map(parseEntity).filter(Boolean);
851
897
  }
852
- return { content: [{ type: "text", text: allFuncs.join("\n") }] };
898
+ const result = { total, entities };
899
+ log(`getEntities: total=${total}, returned=${entities.length}`);
900
+ setCache(cacheKey, result);
901
+ return result;
853
902
  } catch (e) {
854
- return { content: [{ type: "text", text: `\u9519\u8BEF: ${e.message}` }], isError: true };
903
+ log(`getEntities error: ${e.message}`);
904
+ const result = { total: 0, entities: [] };
905
+ setCache(cacheKey, result);
906
+ return result;
855
907
  }
856
908
  }
857
-
858
- // src/handlers/resource-handlers.js
859
- import fs3 from "fs";
860
- import path4 from "path";
861
- import { fileURLToPath as fileURLToPath2 } from "url";
862
- var __dirname2 = path4.dirname(fileURLToPath2(import.meta.url));
863
- var STANDARDS_DIR = path4.join(__dirname2, "..", "standards");
864
- var CACHE_TTL2 = config_default.resourceCacheTtl;
865
- var resourceCache = /* @__PURE__ */ new Map();
866
- function getCached(key) {
867
- const entry = resourceCache.get(key);
868
- if (entry && Date.now() - entry.timestamp < CACHE_TTL2) {
869
- return entry.data;
909
+ async function getTexts(params = {}) {
910
+ const cacheKey = `dwg:texts:${JSON.stringify(params)}`;
911
+ const cached = getCached(cacheKey);
912
+ if (cached) return cached;
913
+ if (!cad.connected) await cad.connect();
914
+ if (!cad.connected) {
915
+ const result = { total: 0, texts: [] };
916
+ setCache(cacheKey, result);
917
+ return result;
870
918
  }
871
- resourceCache.delete(key);
872
- return null;
873
- }
874
- function setCache(key, data) {
875
- resourceCache.set(key, { data, timestamp: Date.now() });
876
- }
877
- function clearCache(uri) {
878
- if (uri) {
879
- for (const key of resourceCache.keys()) {
880
- if (key.startsWith(uri.split("://")[1]?.split("/")[0] + ":")) {
881
- resourceCache.delete(key);
882
- }
919
+ const offset = Math.max(0, parseInt(params.offset) || 0);
920
+ const limit = Math.min(Math.max(1, parseInt(params.limit) || 5e3), 5e3);
921
+ try {
922
+ const code = buildTextCode({
923
+ layer: params.layer || null,
924
+ bbox: params.bbox || null,
925
+ offset,
926
+ limit
927
+ });
928
+ const resultStr = await cad.sendCommandWithResult(code);
929
+ if (!resultStr || resultStr === "" || resultStr === "nil" || resultStr === "0") {
930
+ const result = { total: 0, offset, limit, texts: [] };
931
+ setCache(cacheKey, result);
932
+ return result;
883
933
  }
884
- } else {
885
- resourceCache.clear();
934
+ const parsed = parseLispRecursive(resultStr);
935
+ const textsIdx = parsed.indexOf("texts");
936
+ const rawTexts = textsIdx !== -1 && Array.isArray(parsed[textsIdx + 1]) ? parsed[textsIdx + 1] : [];
937
+ const data = lispPairsToObject(parsed);
938
+ data.texts = rawTexts.length > 0 ? rawTexts.map(parseEntity).filter(Boolean) : [];
939
+ data.offset = offset;
940
+ data.limit = limit;
941
+ setCache(cacheKey, data);
942
+ return data;
943
+ } catch (e) {
944
+ log(`getTexts error: ${e.message}`);
945
+ const result = { total: 0, offset, limit, texts: [] };
946
+ setCache(cacheKey, result);
947
+ return result;
886
948
  }
887
949
  }
888
- function parseLispList(str) {
889
- if (!str || typeof str !== "string") return null;
890
- const trimmed = str.trim();
891
- if (!trimmed.startsWith("(") || !trimmed.endsWith(")")) return null;
892
- const content = trimmed.slice(1, -1).trim();
893
- if (!content) return [];
894
- const result = [];
895
- let depth = 0;
896
- let current = "";
897
- let inString = false;
898
- let escaped = false;
899
- for (let i = 0; i < content.length; i++) {
900
- const ch = content[i];
901
- if (escaped) {
902
- current += ch;
903
- escaped = false;
904
- continue;
905
- }
906
- if (ch === "\\") {
907
- current += ch;
908
- escaped = true;
909
- continue;
950
+ async function getDwgName() {
951
+ if (!cad.connected) await cad.connect();
952
+ if (!cad.connected) return { name: null };
953
+ try {
954
+ const code = `(vl-princ-to-string (getvar "dwgname"))`;
955
+ const name = await cad.sendCommandWithResult(code);
956
+ return { name: name || null };
957
+ } catch (e) {
958
+ return { name: null };
959
+ }
960
+ }
961
+ async function getDwgPath() {
962
+ if (!cad.connected) await cad.connect();
963
+ if (!cad.connected) return { path: null, name: null };
964
+ try {
965
+ const code = `(progn
966
+ (setq prefix (vl-princ-to-string (getvar "dwgprefix")))
967
+ (setq name (vl-princ-to-string (getvar "dwgname")))
968
+ (list (strcat prefix name) name)
969
+ )`;
970
+ const result = await cad.sendCommandWithResult(code);
971
+ if (!result || result === "nil" || result === "") {
972
+ return { path: null, name: null };
910
973
  }
911
- if (ch === '"') {
912
- current += ch;
913
- inString = !inString;
914
- continue;
974
+ let parsed = null;
975
+ try {
976
+ parsed = JSON.parse(result);
977
+ } catch {
915
978
  }
916
- if (inString) {
917
- current += ch;
918
- continue;
979
+ if (!parsed) {
980
+ const listResult = parseLispList(result);
981
+ if (listResult && listResult.length >= 2) parsed = listResult;
919
982
  }
920
- if (ch === "(" || ch === "[") {
921
- current += ch;
922
- depth++;
923
- } else if (ch === ")" || ch === "]") {
924
- current += ch;
925
- depth--;
926
- } else if (ch === " " && depth === 0) {
927
- if (current.trim()) {
928
- result.push(current.trim());
929
- current = "";
930
- }
931
- continue;
983
+ if (parsed && Array.isArray(parsed) && parsed.length >= 2) {
984
+ return { path: String(parsed[0]), name: String(parsed[1]) };
932
985
  }
933
- current += ch;
986
+ return { path: result, name: result };
987
+ } catch (e) {
988
+ return { path: null, name: null };
934
989
  }
935
- if (current.trim()) result.push(current.trim());
936
- return result.map((item) => {
937
- const t = item.trim();
938
- if (t === "nil") return null;
939
- if (t === "t") return true;
940
- if (t === "T") return true;
941
- const num = Number(t);
942
- if (!isNaN(num) && t !== "") return num;
943
- if (t.startsWith('"') && t.endsWith('"')) return t.slice(1, -1);
944
- return t;
945
- });
946
990
  }
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++;
991
+ async function getPackages(filterName = null) {
992
+ const cacheKey = filterName ? `packages:${filterName}` : "packages:all";
993
+ const cached = getCached(cacheKey);
994
+ if (cached) return cached;
995
+ if (!cad.connected) await cad.connect();
996
+ if (!cad.connected) return [];
997
+ try {
998
+ const code = `(mapcar 'cdr @::*local-pkgs*)`;
999
+ const result = await cad.sendCommandWithResult(code);
1000
+ if (!result || result === "" || result === "nil") {
1001
+ setCache(cacheKey, []);
1002
+ return [];
982
1003
  }
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);
1004
+ let raw = [];
1005
+ try {
1006
+ raw = JSON.parse(result);
1007
+ } catch {
1008
+ raw = parseLispList(result) || [];
1009
+ }
1010
+ if (!Array.isArray(raw)) raw = [];
1011
+ const packages = raw.map((item) => {
1012
+ if (Array.isArray(item)) {
1013
+ const pkg = { name: "", version: "0.0.0", description: "", loaded: true };
1014
+ for (const entry of item) {
1015
+ if (entry && typeof entry === "object" && !Array.isArray(entry)) {
1016
+ const [[key, val]] = Object.entries(entry);
1017
+ const k = String(key);
1018
+ const v = String(val);
1019
+ if (k === ":NAME") pkg.name = v;
1020
+ else if (k === ":VERSION") pkg.version = v;
1021
+ else if (k === ":DESCRIPTION") pkg.description = v;
1022
+ else if (k === ":FULL-NAME") pkg.fullName = v;
1023
+ else if (k === ":AUTHOR") pkg.author = v;
1024
+ else if (k === ":CATEGORY") pkg.category = v;
1025
+ else if (k === ":URL") pkg.url = v;
1026
+ }
1018
1027
  }
1019
- } else {
1020
- items.push(left);
1028
+ return pkg;
1021
1029
  }
1030
+ if (typeof item === "string" || typeof item === "number") {
1031
+ return { name: String(item), version: "0.0.0", loaded: true };
1032
+ }
1033
+ if (item && typeof item === "object") {
1034
+ const pkg = { name: "", version: "0.0.0", description: "", loaded: true };
1035
+ const nameVal = item.name || item.Name || item.NAME || item[":NAME"];
1036
+ if (nameVal) pkg.name = String(nameVal);
1037
+ const verVal = item.version || item.Version || item.VERSION || item[":VERSION"];
1038
+ if (verVal) pkg.version = String(verVal);
1039
+ const descVal = item.description || item.Description || item.DESCRIPTION || item[":DESCRIPTION"];
1040
+ if (descVal) pkg.description = String(descVal);
1041
+ if (item.fullName || item.full_name) pkg.fullName = String(item.fullName || item.full_name);
1042
+ if (item.author || item.Author) pkg.author = String(item.author || item.Author);
1043
+ if (item.category || item.Category) pkg.category = String(item.category || item.Category);
1044
+ if (item.url || item.Url || item.URL) pkg.url = String(item.url || item.Url || item.URL);
1045
+ return pkg;
1046
+ }
1047
+ return null;
1048
+ }).filter(Boolean);
1049
+ if (filterName) {
1050
+ const filtered = packages.filter((p) => p.name === filterName);
1051
+ setCache(cacheKey, filtered);
1052
+ return filtered;
1022
1053
  }
1023
- return items;
1054
+ setCache(cacheKey, packages);
1055
+ return packages;
1056
+ } catch (e) {
1057
+ setCache(cacheKey, []);
1058
+ return [];
1024
1059
  }
1025
- return parseValue();
1026
1060
  }
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;
1061
+ function getPlatforms() {
1062
+ return CAD_PLATFORMS.map((name) => ({
1063
+ name,
1064
+ extensions: FILE_EXTENSIONS[name] || []
1065
+ }));
1038
1066
  }
1039
- function parseEntity(arr) {
1040
- if (!Array.isArray(arr) || arr.length < 5) return null;
1041
- const [type, handle, layer, color, linetype, ...rest] = arr;
1042
- const entity = {
1043
- type: String(type).toUpperCase().replace(/^"|"$/g, ""),
1044
- handle: String(handle),
1045
- layer: String(layer),
1046
- color,
1047
- linetype: String(linetype)
1048
- };
1049
- const entType = typeof entity.type === "string" ? entity.type.toUpperCase() : entity.type;
1050
- if (/^(TEXT|MTEXT|ATTRIB|TCH_TEXT)$/.test(entType) && rest[0] != null) {
1051
- entity.text = String(rest[0]);
1052
- }
1053
- if (/^(LINE|LWPOLYLINE|POLYLINE|ARC|CIRCLE|SPLINE|ELLIPSE|XLINE|RAY)$/.test(entType) && Array.isArray(rest[0])) {
1054
- entity.points = rest[0].map((pt) => {
1055
- if (Array.isArray(pt) && pt.length >= 2)
1056
- return { x: Number(pt[0]), y: Number(pt[1]), z: Number(pt[2] || 0) };
1067
+ async function getDwgList() {
1068
+ const cacheKey = "cad:dwgs";
1069
+ const cached = getCached(cacheKey);
1070
+ if (cached) return cached;
1071
+ if (!cad.connected) await cad.connect();
1072
+ if (!cad.connected) return [];
1073
+ try {
1074
+ const code = `(progn
1075
+ (vl-load-com)
1076
+ (setq result nil)
1077
+ (vlax-for doc (vla-get-Documents (vla-get-Application (vlax-get-acad-object)))
1078
+ (setq result (cons (list (vla-get-Name doc) (vla-get-FullName doc)) result))
1079
+ )
1080
+ (reverse result)
1081
+ )`;
1082
+ const result = await cad.sendCommandWithResult(code);
1083
+ if (!result || result === "" || result === "nil") {
1084
+ setCache(cacheKey, []);
1085
+ return [];
1086
+ }
1087
+ let parsed = [];
1088
+ try {
1089
+ parsed = JSON.parse(result);
1090
+ } catch (e) {
1091
+ const listResult = parseLispList(result);
1092
+ if (listResult) {
1093
+ parsed = listResult;
1094
+ }
1095
+ }
1096
+ if (!Array.isArray(parsed)) parsed = [];
1097
+ const dwgList = parsed.map((item) => {
1098
+ if (Array.isArray(item) && item.length >= 2) {
1099
+ return { name: String(item[0]), path: String(item[1]) };
1100
+ }
1057
1101
  return null;
1058
1102
  }).filter(Boolean);
1103
+ setCache(cacheKey, dwgList);
1104
+ return dwgList;
1105
+ } catch (e) {
1106
+ setCache(cacheKey, []);
1107
+ return [];
1059
1108
  }
1060
- if (entType === "INSERT" && rest[0] != null) {
1061
- entity.blockName = String(rest[0]);
1109
+ }
1110
+ function loadStandardsFile(filename) {
1111
+ const filePath = path3.join(STANDARDS_DIR, filename);
1112
+ try {
1113
+ const raw = fs2.readFileSync(filePath, "utf-8");
1114
+ return JSON.parse(raw);
1115
+ } catch (e) {
1116
+ log(`Error loading standards file ${filename}: ${e.message}`);
1117
+ return null;
1062
1118
  }
1063
- return entity;
1064
1119
  }
1065
- function buildEntityCode({ type, layer, bbox }) {
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
- );
1120
+ var standardsCache = /* @__PURE__ */ new Map();
1121
+ function getCachedStandards(key) {
1122
+ const entry = standardsCache.get(key);
1123
+ if (entry && Date.now() - entry.timestamp < 3e4) return entry.data;
1124
+ standardsCache.delete(key);
1125
+ return null;
1126
+ }
1127
+ function setCachedStandards(key, data) {
1128
+ standardsCache.set(key, { data, timestamp: Date.now() });
1129
+ }
1130
+ function getDraftingStandards() {
1131
+ const cached = getCachedStandards("drafting");
1132
+ if (cached) return cached;
1133
+ const data = loadStandardsFile("drafting.json");
1134
+ if (data) setCachedStandards("drafting", data);
1135
+ return data || { error: "\u5236\u56FE\u89C4\u8303\u6587\u4EF6\u52A0\u8F7D\u5931\u8D25" };
1136
+ }
1137
+ function getCodingStandards() {
1138
+ const cached = getCachedStandards("coding");
1139
+ if (cached) return cached;
1140
+ const data = loadStandardsFile("coding.json");
1141
+ if (data) setCachedStandards("coding", data);
1142
+ return data || { error: "\u7F16\u7801\u89C4\u8303\u6587\u4EF6\u52A0\u8F7D\u5931\u8D25" };
1143
+ }
1144
+
1145
+ // src/session-manager.js
1146
+ var Session = class {
1147
+ constructor(clientId) {
1148
+ this.clientId = clientId;
1149
+ this.subscriptions = /* @__PURE__ */ new Set();
1150
+ this.createdAt = Date.now();
1151
+ this.connected = false;
1152
+ }
1153
+ };
1154
+ var SessionManager = class {
1155
+ #sessions = /* @__PURE__ */ new Map();
1156
+ createSession(clientId) {
1157
+ let session = this.#sessions.get(clientId);
1158
+ if (!session) {
1159
+ session = new Session(clientId);
1160
+ this.#sessions.set(clientId, session);
1079
1161
  }
1162
+ return session;
1080
1163
  }
1081
- const filterExpr = items.length ? `(list ${items.join(" ")})` : "nil";
1082
- const ssgetExpr = items.length ? `(ssget "_X" ${filterExpr})` : '(ssget "_X")';
1083
- return `(progn
1084
- (vl-load-com)
1085
- (defun @e:safe-fn (f ent / r)
1086
- (setq r (vl-catch-all-apply f (list ent)))
1087
- (if (vl-catch-all-error-p r) nil r))
1088
- (defun @e:dp (ent / ed typ gcl is)
1089
- (setq ed (entget ent) typ (cdr (assoc 0 ed)) gcl (assoc 62 ed) is (assoc 6 ed))
1090
- (append
1091
- (list typ (cdr (assoc 5 ed)) (cdr (assoc 8 ed))
1092
- (if gcl (cdr gcl) 256)
1093
- (if is (cdr is) "ByLayer"))
1094
- (cond
1095
- ((wcmatch typ "TEXT,MTEXT,ATTRIB,TCH_TEXT")
1096
- (list (@e:safe-fn '(lambda (e) (text:remove-fmt (text:get-mtext e))) ent)))
1097
- ((wcmatch typ "LINE,LWPOLYLINE,POLYLINE,ARC,CIRCLE,SPLINE,ELLIPSE,XLINE,RAY")
1098
- (list (@e:safe-fn 'curve:get-points ent)))
1099
- ((= typ "INSERT")
1100
- (list (@e:safe-fn 'block:get-effectivename ent)))
1101
- (t nil))))
1102
- (setq @e:total (if (setq @e:ss ${ssgetExpr}) (sslength @e:ss) 0)
1103
- @e:ents nil @e:i 0)
1104
- (while (and @e:ss (< @e:i @e:total)(< (length @e:ents) 3000))
1105
- (setq @e:ents (cons (@e:dp (ssname @e:ss @e:i)) @e:ents) @e:i (1+ @e:i)))
1106
- (vl-prin1-to-string (list @e:total (reverse @e:ents))))`;
1107
- }
1108
- function buildTextCode({ layer, bbox, offset, limit }) {
1109
- const items = [`'(0 . "TEXT,MTEXT,ATTRIB,TCH_TEXT")`];
1110
- if (layer) items.push(`(cons 8 "${layer.replace(/"/g, '\\"')}")`);
1111
- if (bbox) {
1112
- const parts = bbox.split(",").map((s) => parseFloat(s.trim()));
1113
- if (parts.length === 4) {
1114
- const [x1, y1, x2, y2] = parts;
1115
- items.push(
1116
- `'(-4 . "<AND")`,
1117
- `'(-4 . ">=,>=,*") (list 10 ${x1} ${y1} 0.0)`,
1118
- `'(-4 . "<=,<=,*") (list 10 ${x2} ${y2} 0.0)`,
1119
- `'(-4 . "AND>")`
1120
- );
1164
+ getSession(clientId) {
1165
+ return this.#sessions.get(clientId) || null;
1166
+ }
1167
+ hasSession(clientId) {
1168
+ return this.#sessions.has(clientId);
1169
+ }
1170
+ removeSession(clientId) {
1171
+ return this.#sessions.delete(clientId);
1172
+ }
1173
+ subscribe(clientId, uri) {
1174
+ const session = this.#sessions.get(clientId);
1175
+ if (session) {
1176
+ session.subscriptions.add(uri);
1177
+ return true;
1121
1178
  }
1179
+ return false;
1122
1180
  }
1123
- const filterExpr = `(list ${items.join(" ")})`;
1124
- return `(progn
1125
- (defun @t:props (ent / ed typ result item ins)
1126
- (setq ed (entget ent) typ (cdr (assoc 0 ed)))
1127
- (setq result (list (vl-prin1-to-string typ) (vl-prin1-to-string (cdr (assoc 5 ed))) (vl-prin1-to-string (cdr (assoc 8 ed)))
1128
- (if (setq item (assoc 62 ed)) (cdr item) "ByLayer")
1129
- (vl-prin1-to-string (if (setq item (assoc 6 ed)) (cdr item) "ByLayer"))))
1130
- (setq ins (cdr (assoc 10 ed)))
1131
- (append result (list (car ins) (cadr ins) (caddr ins)
1132
- (vl-prin1-to-string (cdr (assoc 1 ed))) (cdr (assoc 40 ed)) (cdr (assoc 50 ed))
1133
- (vl-prin1-to-string (if (setq item (assoc 7 ed)) (cdr item) "Standard")))))
1134
- (defun @t:run nil
1135
- (setq ss (ssget "_X" ${filterExpr}))
1136
- (if (null ss)
1137
- (list "total" 0 "offset" ${offset} "limit" ${limit})
1138
- (progn
1139
- (setq total (sslength ss) texts nil i ${offset})
1140
- (while (and (< i total) (< (length texts) ${limit}))
1141
- (setq texts (append texts (list (@t:props (ssname ss i)))) i (1+ i)))
1142
- (list "total" total "offset" ${offset} "limit" ${limit} "texts" texts))))
1143
- (@t:run))
1144
- `;
1181
+ unsubscribe(clientId, uri) {
1182
+ const session = this.#sessions.get(clientId);
1183
+ if (session) {
1184
+ return session.subscriptions.delete(uri);
1185
+ }
1186
+ return false;
1187
+ }
1188
+ getSubscriptions(clientId) {
1189
+ const session = this.#sessions.get(clientId);
1190
+ return session ? Array.from(session.subscriptions) : [];
1191
+ }
1192
+ isSubscribed(clientId, uri) {
1193
+ const session = this.#sessions.get(clientId);
1194
+ return session ? session.subscriptions.has(uri) : false;
1195
+ }
1196
+ getAllSubscribedUris() {
1197
+ const all = /* @__PURE__ */ new Set();
1198
+ for (const session of this.#sessions.values()) {
1199
+ for (const uri of session.subscriptions) {
1200
+ all.add(uri);
1201
+ }
1202
+ }
1203
+ return all;
1204
+ }
1205
+ list() {
1206
+ return Array.from(this.#sessions.entries()).map(([id, s]) => ({
1207
+ clientId: id,
1208
+ subscriptions: Array.from(s.subscriptions),
1209
+ createdAt: s.createdAt,
1210
+ connected: s.connected
1211
+ }));
1212
+ }
1213
+ count() {
1214
+ return this.#sessions.size;
1215
+ }
1216
+ clear() {
1217
+ this.#sessions.clear();
1218
+ }
1219
+ };
1220
+
1221
+ // src/subscription-manager.js
1222
+ var sessionManager = new SessionManager();
1223
+ var _notifyFn = null;
1224
+ function setNotify(fn) {
1225
+ _notifyFn = fn;
1145
1226
  }
1146
- var RESOURCES = [
1147
- { uri: "atlisp://cad/info", name: "CAD Info", description: "CAD \u8FDE\u63A5\u4FE1\u606F", mimeType: "application/json", subscribable: true },
1148
- { uri: "atlisp://dwg/layers", name: "Layers", description: "\u56FE\u5C42\u5217\u8868", mimeType: "application/json", subscribable: true },
1149
- { uri: "atlisp://dwg/entities", name: "Entities", description: "\u56FE\u5143\u5217\u8868\uFF0C\u8FD4\u56DE\u603B\u6570\u548C\u524D3000\u4E2A\u56FE\u5143\uFF0C\u6587\u672C\u9644\u52A0 text \u5B57\u6BB5\uFF0C\u66F2\u7EBF\u9644\u52A0 points \u6570\u7EC4\uFF0CINSERT \u9644\u52A0 blockName\uFF0C\u652F\u6301 ?type=LINE&layer=0&bbox=0,0,100,100 \u8FC7\u6EE4", mimeType: "application/json", subscribable: true },
1150
- { 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=5000&offset=0", mimeType: "application/json", subscribable: true },
1151
- { uri: "atlisp://dwg/name", name: "DWG Name", description: "\u5F53\u524D DWG \u6587\u4EF6\u540D", mimeType: "application/json", subscribable: true },
1152
- { uri: "atlisp://dwg/path", name: "DWG Path", description: "\u6587\u4EF6\u5B8C\u6574\u8DEF\u5F84", mimeType: "application/json", subscribable: true },
1153
- { uri: "atlisp://cad/dwgs", name: "DWG List", description: "\u6240\u6709\u6253\u5F00\u7684 DWG \u6587\u4EF6\u5217\u8868", mimeType: "application/json", subscribable: true },
1154
- { uri: "atlisp://packages", name: "Packages", description: "\u5DF2\u5B89\u88C5\u5305\u5217\u8868", mimeType: "application/json", subscribable: true },
1155
- { uri: "atlisp://platforms", name: "Platforms", description: "\u652F\u6301\u7684\u5E73\u53F0\u4FE1\u606F", mimeType: "application/json", subscribable: false },
1156
- { uri: "atlisp://standards/drafting", name: "Drafting Standards", description: "CAD \u5236\u56FE\u89C4\u8303", mimeType: "application/json", subscribable: false },
1157
- { uri: "atlisp://standards/coding", name: "Coding Standards", description: "@lisp \u7F16\u7801\u89C4\u8303", mimeType: "application/json", subscribable: false }
1158
- ];
1159
- function parseQuery(uri) {
1160
- const qIdx = uri.indexOf("?");
1161
- if (qIdx === -1) return { base: uri, params: {} };
1162
- const base = uri.substring(0, qIdx);
1163
- const params = {};
1164
- uri.substring(qIdx + 1).split("&").forEach((p) => {
1165
- const eqIdx = p.indexOf("=");
1166
- const k = eqIdx === -1 ? p : p.substring(0, eqIdx);
1167
- const v = eqIdx === -1 ? "" : p.substring(eqIdx + 1);
1168
- if (k) params[k] = decodeURIComponent(v);
1169
- });
1170
- return { base, params };
1227
+ function subscribe(uri, sessionId) {
1228
+ if (sessionId) {
1229
+ return sessionManager.subscribe(sessionId, uri);
1230
+ }
1231
+ return true;
1171
1232
  }
1172
- async function listResources(subscribedUris = []) {
1173
- return RESOURCES.map((r) => ({
1174
- uri: r.uri,
1175
- name: r.name,
1176
- description: r.description,
1177
- mimeType: r.mimeType,
1178
- subscribed: subscribedUris.includes(r.uri)
1179
- }));
1233
+ function unsubscribe(uri, sessionId) {
1234
+ if (sessionId) {
1235
+ return sessionManager.unsubscribe(sessionId, uri);
1236
+ }
1237
+ return true;
1180
1238
  }
1181
- async function readResource(uri) {
1182
- const { base, params } = parseQuery(uri);
1183
- switch (base) {
1184
- case "atlisp://cad/info":
1185
- return await getCadInfo2();
1186
- case "atlisp://dwg/layers":
1187
- return await getLayers(params.name);
1188
- case "atlisp://dwg/entities":
1189
- return await getEntities(params);
1190
- case "atlisp://dwg/texts":
1191
- return await getTexts(params);
1192
- case "atlisp://dwg/name":
1193
- return await getDwgName();
1194
- case "atlisp://dwg/path":
1195
- return await getDwgPath();
1196
- case "atlisp://cad/dwgs":
1197
- return await getDwgList();
1198
- case "atlisp://packages":
1199
- return await getPackages(params.name);
1200
- case "atlisp://platforms":
1201
- return getPlatforms();
1202
- case "atlisp://standards/drafting":
1203
- return getDraftingStandards();
1204
- case "atlisp://standards/coding":
1205
- return getCodingStandards();
1206
- default:
1207
- throw new Error(`\u8D44\u6E90\u4E0D\u5B58\u5728: ${uri}`);
1239
+ function list(sessionId) {
1240
+ if (sessionId) {
1241
+ return sessionManager.getSubscriptions(sessionId);
1242
+ }
1243
+ return Array.from(sessionManager.getAllSubscribedUris());
1244
+ }
1245
+ function isSubscribed(uri, sessionId) {
1246
+ if (sessionId) {
1247
+ return sessionManager.isSubscribed(sessionId, uri);
1248
+ }
1249
+ return sessionManager.getAllSubscribedUris().has(uri);
1250
+ }
1251
+ function notify(uri, data) {
1252
+ if (_notifyFn) {
1253
+ _notifyFn(uri, data);
1254
+ }
1255
+ }
1256
+
1257
+ // src/sse-session.js
1258
+ import { randomUUID as randomUUID2 } from "crypto";
1259
+ var DEFAULT_HEARTBEAT_INTERVAL = 3e4;
1260
+ var DEFAULT_RETRY_INTERVAL = 3e4;
1261
+ var MAX_SENT_MESSAGES = 1e3;
1262
+ var SSE_EVENTS = {
1263
+ MESSAGE: "message",
1264
+ ENDPOINT: "endpoint",
1265
+ PING: "ping",
1266
+ ERROR: "error",
1267
+ PROGRESS: "progress",
1268
+ CAPABILITIES: "capabilities"
1269
+ };
1270
+ var SseSession = class {
1271
+ #res;
1272
+ #clientId;
1273
+ #active = true;
1274
+ #heartbeatTimer = null;
1275
+ #lastEventId = 0;
1276
+ #heartbeatInterval;
1277
+ #retryInterval;
1278
+ #sessionData = {};
1279
+ #sentMessages = [];
1280
+ #backpressureBuffer = [];
1281
+ #drainHandler = null;
1282
+ constructor(res, options = {}) {
1283
+ this.#res = res;
1284
+ this.#clientId = options.clientId || randomUUID2();
1285
+ this.#heartbeatInterval = options.heartbeatInterval || DEFAULT_HEARTBEAT_INTERVAL;
1286
+ this.#retryInterval = options.retryInterval || DEFAULT_RETRY_INTERVAL;
1287
+ this.#sessionData = options.sessionData || {};
1288
+ if (options.resumeFromId && options.resumeFromId > 0) {
1289
+ this.#lastEventId = options.resumeFromId;
1290
+ }
1291
+ this._setupSseHeaders();
1292
+ this._sendRetry();
1293
+ this._setupDrain();
1294
+ }
1295
+ get clientId() {
1296
+ return this.#clientId;
1208
1297
  }
1209
- }
1210
- async function getCadInfo2() {
1211
- const cached = getCached("cad:info");
1212
- if (cached) return cached;
1213
- if (!cad.connected) {
1214
- await cad.connect();
1298
+ get isActive() {
1299
+ return this.#active;
1215
1300
  }
1216
- if (!cad.connected) {
1217
- const result2 = { connected: false, platform: null, version: null, busy: false };
1218
- setCache("cad:info", result2);
1219
- return result2;
1301
+ get lastEventId() {
1302
+ return this.#lastEventId;
1220
1303
  }
1221
- let busy = false;
1222
- try {
1223
- busy = await cad.isBusy();
1224
- } catch (e) {
1225
- log(`Error checking CAD busy status: ${e.message}`);
1304
+ get sessionData() {
1305
+ return this.#sessionData;
1226
1306
  }
1227
- const result = {
1228
- connected: true,
1229
- platform: cad.getPlatform(),
1230
- version: cad.getVersion(),
1231
- busy
1232
- };
1233
- setCache("cad:info", result);
1234
- return result;
1235
- }
1236
- async function getLayers(filterName = null) {
1237
- const cacheKey = filterName ? `dwg:layers:${filterName}` : "dwg:layers:all";
1238
- const cached = getCached(cacheKey);
1239
- if (cached) return cached;
1240
- if (!cad.connected) await cad.connect();
1241
- if (!cad.connected) return [];
1242
- try {
1243
- const code = filterName ? `(vl-prin1-to-string (cdr (assoc 2 (tblnext "LAYER" T))))` : `(progn (setq res nil)(while (setq e (tblnext "LAYER" (null res)))(setq res (cons (list (vl-prin1-to-string (cdr (assoc 2 e))) (cdr (assoc 62 e)) (cdr (assoc 70 e))) res))) (reverse res))`;
1244
- const result = await cad.sendCommandWithResult(code);
1245
- if (!result || result === "" || result === "nil") {
1246
- setCache(cacheKey, []);
1247
- return [];
1307
+ get sentMessages() {
1308
+ return [...this.#sentMessages];
1309
+ }
1310
+ getMessagesSince(eventId) {
1311
+ return this.#sentMessages.filter((m) => m.id > eventId);
1312
+ }
1313
+ setSessionData(key, value) {
1314
+ this.#sessionData[key] = value;
1315
+ }
1316
+ getSessionData(key) {
1317
+ return this.#sessionData[key];
1318
+ }
1319
+ get retryInterval() {
1320
+ return this.#retryInterval;
1321
+ }
1322
+ setRetryInterval(ms) {
1323
+ this.#retryInterval = ms;
1324
+ }
1325
+ _sendRetry() {
1326
+ this._write(`retry: ${this.#retryInterval}
1327
+
1328
+ `);
1329
+ }
1330
+ _setupSseHeaders() {
1331
+ this.#res.setHeader("Content-Type", "text/event-stream");
1332
+ this.#res.setHeader("Cache-Control", "no-cache");
1333
+ this.#res.setHeader("Connection", "keep-alive");
1334
+ this.#res.setHeader("Transfer-Encoding", "chunked");
1335
+ this.#res.setHeader("X-Accel-Buffering", "no");
1336
+ if (typeof this.#res.flush !== "function") {
1337
+ this.#res.flush = () => {
1338
+ };
1248
1339
  }
1249
- let layers = [];
1340
+ }
1341
+ _formatEvent(event, data, id = null) {
1342
+ const lines = [];
1343
+ if (id !== null) {
1344
+ lines.push(`id: ${id}`);
1345
+ }
1346
+ lines.push(`event: ${event}`);
1347
+ if (data !== null && data !== void 0) {
1348
+ const jsonStr = JSON.stringify(data);
1349
+ lines.push(`data: ${jsonStr}`);
1350
+ } else {
1351
+ lines.push("data:");
1352
+ }
1353
+ return lines.join("\n") + "\n\n";
1354
+ }
1355
+ _setupDrain() {
1356
+ if (typeof this.#res.on !== "function") return;
1357
+ this.#drainHandler = () => {
1358
+ if (!this.#active) return;
1359
+ if (this.#backpressureBuffer.length > 0) {
1360
+ this._drainBuffer();
1361
+ }
1362
+ };
1363
+ this.#res.on("drain", this.#drainHandler);
1364
+ }
1365
+ _drainBuffer() {
1366
+ const batch = this.#backpressureBuffer.splice(0, 50);
1367
+ for (const item of batch) {
1368
+ const ok = this.#res.write(item);
1369
+ if (!ok) {
1370
+ this.#backpressureBuffer.unshift(...batch.slice(batch.indexOf(item)));
1371
+ return;
1372
+ }
1373
+ }
1374
+ this._flush();
1375
+ if (this.#backpressureBuffer.length > 0) {
1376
+ this._drainBuffer();
1377
+ }
1378
+ }
1379
+ _write(content) {
1380
+ if (!this.#active) return false;
1250
1381
  try {
1251
- layers = JSON.parse(result);
1252
- } catch (e) {
1253
- const parsed = parseLispList(result);
1254
- if (parsed) {
1255
- layers = parsed;
1256
- } else {
1257
- layers = [];
1382
+ if (this.#backpressureBuffer.length > 0) {
1383
+ this.#backpressureBuffer.push(content);
1384
+ return true;
1385
+ }
1386
+ const result = this.#res.write(content);
1387
+ if (typeof this.#res.flush === "function") {
1388
+ this.#res.flush();
1389
+ }
1390
+ if (result === false) {
1391
+ this.#backpressureBuffer.push(content);
1392
+ return true;
1258
1393
  }
1394
+ return true;
1395
+ } catch (e) {
1396
+ this.#active = false;
1397
+ this.close();
1398
+ return false;
1259
1399
  }
1260
- if (!Array.isArray(layers)) {
1261
- if (typeof layers === "number") {
1262
- layers = [[result, layers, 0]];
1263
- } else if (typeof layers === "string") {
1264
- layers = [[layers, 7, 0]];
1400
+ }
1401
+ _flush() {
1402
+ try {
1403
+ if (typeof this.#res.flush === "function") {
1404
+ this.#res.flush();
1265
1405
  }
1406
+ } catch (e) {
1266
1407
  }
1267
- const mapped = layers.map((l) => {
1268
- const name = Array.isArray(l) ? l[0] : l;
1269
- const color = Array.isArray(l) && l[1] !== void 0 ? l[1] : 7;
1270
- const flags = Array.isArray(l) && l[2] !== void 0 ? l[2] : 0;
1271
- return {
1272
- name: String(name),
1273
- color,
1274
- locked: (flags & 4) !== 0,
1275
- frozen: (flags & 1) !== 0
1276
- };
1277
- });
1278
- setCache(cacheKey, mapped);
1279
- return mapped;
1280
- } catch (e) {
1281
- setCache(cacheKey, []);
1282
- return [];
1283
1408
  }
1284
- }
1285
- async function getEntities(params = {}) {
1286
- const cacheKey = `dwg:entities:${JSON.stringify(params)}`;
1287
- const cached = getCached(cacheKey);
1288
- if (cached) return cached;
1289
- if (!cad.connected) await cad.connect();
1290
- if (!cad.connected) {
1291
- const result = { total: 0, entities: [] };
1292
- setCache(cacheKey, result);
1293
- return result;
1409
+ flushBackpressure() {
1410
+ if (this.#backpressureBuffer.length > 0) {
1411
+ this._drainBuffer();
1412
+ }
1294
1413
  }
1295
- try {
1296
- const code = buildEntityCode({
1297
- type: params.type || null,
1298
- layer: params.layer || null,
1299
- bbox: params.bbox || null
1300
- });
1301
- const resultStr = await cad.sendCommandWithResult(code);
1302
- if (!resultStr || resultStr === "" || resultStr === "nil") {
1303
- const result2 = { total: 0, entities: [] };
1304
- setCache(cacheKey, result2);
1305
- return result2;
1414
+ sendEvent(event, data, id = null) {
1415
+ this.#lastEventId++;
1416
+ const eventId = id !== null ? id : this.#lastEventId;
1417
+ const formatted = this._formatEvent(event, data, eventId);
1418
+ this.#sentMessages.push({ event, data, id: eventId, timestamp: Date.now() });
1419
+ if (this.#sentMessages.length > MAX_SENT_MESSAGES) {
1420
+ this.#sentMessages.splice(0, this.#sentMessages.length - MAX_SENT_MESSAGES);
1306
1421
  }
1307
- let total = 0;
1308
- let entities = [];
1309
- const parsed = parseLispRecursive(resultStr);
1310
- if (parsed && Array.isArray(parsed) && parsed.length >= 2) {
1311
- total = typeof parsed[0] === "number" ? parsed[0] : 0;
1312
- const rawEntities = Array.isArray(parsed[1]) ? parsed[1] : [];
1313
- entities = rawEntities.map(parseEntity).filter(Boolean);
1422
+ this._write(formatted);
1423
+ this._flush();
1424
+ return true;
1425
+ }
1426
+ sendMessage(data) {
1427
+ return this.sendEvent(SSE_EVENTS.MESSAGE, data);
1428
+ }
1429
+ sendEndpoint(path9) {
1430
+ return this.sendEvent(SSE_EVENTS.ENDPOINT, path9);
1431
+ }
1432
+ sendError(code, message, id = null) {
1433
+ return this.sendEvent(SSE_EVENTS.ERROR, { code, message }, id);
1434
+ }
1435
+ sendPing() {
1436
+ return this.sendEvent(SSE_EVENTS.PING, { timestamp: Date.now() });
1437
+ }
1438
+ sendProgress(current, total, message = "") {
1439
+ return this.sendEvent(SSE_EVENTS.PROGRESS, { current, total, message });
1440
+ }
1441
+ sendCapabilities(capabilities) {
1442
+ return this.sendEvent(SSE_EVENTS.CAPABILITIES, capabilities);
1443
+ }
1444
+ sendResponse(jsonrpcResponse, id = null) {
1445
+ const eventId = id !== null ? id : this.#lastEventId;
1446
+ return this.sendEvent(SSE_EVENTS.MESSAGE, jsonrpcResponse, eventId);
1447
+ }
1448
+ startHeartbeat(interval = null) {
1449
+ this.stopHeartbeat();
1450
+ const ms = interval || this.#heartbeatInterval;
1451
+ this.#heartbeatTimer = setInterval(() => {
1452
+ if (!this.#active) {
1453
+ this.stopHeartbeat();
1454
+ return;
1455
+ }
1456
+ if (!this.sendPing()) {
1457
+ this.stopHeartbeat();
1458
+ this.close();
1459
+ }
1460
+ }, ms);
1461
+ }
1462
+ stopHeartbeat() {
1463
+ if (this.#heartbeatTimer) {
1464
+ clearInterval(this.#heartbeatTimer);
1465
+ this.#heartbeatTimer = null;
1466
+ }
1467
+ }
1468
+ close() {
1469
+ this.#active = false;
1470
+ this.stopHeartbeat();
1471
+ if (this.#drainHandler) {
1472
+ this.#res.removeListener("drain", this.#drainHandler);
1473
+ this.#drainHandler = null;
1474
+ }
1475
+ this.#backpressureBuffer = [];
1476
+ try {
1477
+ this.#res.end();
1478
+ } catch (e) {
1314
1479
  }
1315
- const result = { total, entities };
1316
- log(`getEntities: total=${total}, returned=${entities.length}`);
1317
- setCache(cacheKey, result);
1318
- return result;
1319
- } catch (e) {
1320
- log(`getEntities error: ${e.message}`);
1321
- const result = { total: 0, entities: [] };
1322
- setCache(cacheKey, result);
1323
- return result;
1324
1480
  }
1481
+ };
1482
+
1483
+ // src/sse-session-manager.js
1484
+ import fs3 from "fs";
1485
+ import path4 from "path";
1486
+ import os2 from "os";
1487
+ var SESSION_STORE_DIR = path4.join(os2.homedir(), ".atlisp", "sessions");
1488
+ var SESSION_STORE_TTL = 24 * 60 * 60 * 1e3;
1489
+ function getSessionStorePath(clientId) {
1490
+ return path4.join(SESSION_STORE_DIR, `${clientId}.json`);
1325
1491
  }
1326
- async function getTexts(params = {}) {
1327
- const cacheKey = `dwg:texts:${JSON.stringify(params)}`;
1328
- const cached = getCached(cacheKey);
1329
- if (cached) return cached;
1330
- if (!cad.connected) await cad.connect();
1331
- if (!cad.connected) {
1332
- const result = { total: 0, texts: [] };
1333
- setCache(cacheKey, result);
1334
- return result;
1335
- }
1336
- const offset = Math.max(0, parseInt(params.offset) || 0);
1337
- const limit = Math.min(Math.max(1, parseInt(params.limit) || 5e3), 5e3);
1492
+ function persistSessionData(clientId, sessionData) {
1338
1493
  try {
1339
- const code = buildTextCode({
1340
- layer: params.layer || null,
1341
- bbox: params.bbox || null,
1342
- offset,
1343
- limit
1344
- });
1345
- const resultStr = await cad.sendCommandWithResult(code);
1346
- if (!resultStr || resultStr === "" || resultStr === "nil" || resultStr === "0") {
1347
- const result = { total: 0, offset, limit, texts: [] };
1348
- setCache(cacheKey, result);
1349
- return result;
1350
- }
1351
- const parsed = parseLispRecursive(resultStr);
1352
- const textsIdx = parsed.indexOf("texts");
1353
- const rawTexts = textsIdx !== -1 && Array.isArray(parsed[textsIdx + 1]) ? parsed[textsIdx + 1] : [];
1354
- const data = lispPairsToObject(parsed);
1355
- data.texts = rawTexts.length > 0 ? rawTexts.map(parseEntity).filter(Boolean) : [];
1356
- data.offset = offset;
1357
- data.limit = limit;
1358
- setCache(cacheKey, data);
1359
- return data;
1494
+ fs3.mkdirSync(SESSION_STORE_DIR, { recursive: true });
1495
+ const store = {
1496
+ clientId,
1497
+ sessionData,
1498
+ savedAt: Date.now()
1499
+ };
1500
+ fs3.writeFileSync(getSessionStorePath(clientId), JSON.stringify(store), "utf-8");
1360
1501
  } catch (e) {
1361
- log(`getTexts error: ${e.message}`);
1362
- const result = { total: 0, offset, limit, texts: [] };
1363
- setCache(cacheKey, result);
1364
- return result;
1502
+ console.error("Failed to persist session:", e.message);
1365
1503
  }
1366
1504
  }
1367
- async function getDwgName() {
1368
- if (!cad.connected) await cad.connect();
1369
- if (!cad.connected) return { name: null };
1505
+ function loadPersistedSessionData(clientId) {
1370
1506
  try {
1371
- const code = `(vl-princ-to-string (getvar "dwgname"))`;
1372
- const name = await cad.sendCommandWithResult(code);
1373
- return { name: name || null };
1507
+ const filePath = getSessionStorePath(clientId);
1508
+ if (!fs3.existsSync(filePath)) return null;
1509
+ const raw = fs3.readFileSync(filePath, "utf-8");
1510
+ const store = JSON.parse(raw);
1511
+ if (Date.now() - store.savedAt > SESSION_STORE_TTL) {
1512
+ fs3.unlinkSync(filePath);
1513
+ return null;
1514
+ }
1515
+ return store.sessionData || {};
1374
1516
  } catch (e) {
1375
- return { name: null };
1517
+ return null;
1376
1518
  }
1377
1519
  }
1378
- async function getDwgPath() {
1379
- if (!cad.connected) await cad.connect();
1380
- if (!cad.connected) return { path: null, name: null };
1381
- try {
1382
- const code = `(progn
1383
- (setq prefix (vl-princ-to-string (getvar "dwgprefix")))
1384
- (setq name (vl-princ-to-string (getvar "dwgname")))
1385
- (list (strcat prefix name) name)
1386
- )`;
1387
- const result = await cad.sendCommandWithResult(code);
1388
- if (!result || result === "nil" || result === "") {
1389
- return { path: null, name: null };
1520
+ var SseSessionManager = class {
1521
+ #sessions = /* @__PURE__ */ new Map();
1522
+ #eventHandlers = /* @__PURE__ */ new Map();
1523
+ constructor() {
1524
+ this.#sessions = /* @__PURE__ */ new Map();
1525
+ this.#eventHandlers = /* @__PURE__ */ new Map();
1526
+ }
1527
+ add(clientId, session) {
1528
+ if (!(session instanceof SseSession)) {
1529
+ throw new Error("session must be an SseSession instance");
1390
1530
  }
1391
- let parsed = null;
1392
- try {
1393
- parsed = JSON.parse(result);
1394
- } catch {
1531
+ const persisted = loadPersistedSessionData(clientId);
1532
+ if (persisted) {
1533
+ for (const [key, value] of Object.entries(persisted)) {
1534
+ session.setSessionData(key, value);
1535
+ }
1395
1536
  }
1396
- if (!parsed) {
1397
- const listResult = parseLispList(result);
1398
- if (listResult && listResult.length >= 2) parsed = listResult;
1537
+ this.#sessions.set(clientId, session);
1538
+ session.startHeartbeat();
1539
+ }
1540
+ remove(clientId) {
1541
+ const session = this.#sessions.get(clientId);
1542
+ if (session) {
1543
+ persistSessionData(clientId, session.sessionData);
1544
+ session.close();
1545
+ this.#sessions.delete(clientId);
1546
+ return true;
1399
1547
  }
1400
- if (parsed && Array.isArray(parsed) && parsed.length >= 2) {
1401
- return { path: String(parsed[0]), name: String(parsed[1]) };
1548
+ return false;
1549
+ }
1550
+ get(clientId) {
1551
+ return this.#sessions.get(clientId) || null;
1552
+ }
1553
+ has(clientId) {
1554
+ return this.#sessions.has(clientId);
1555
+ }
1556
+ list() {
1557
+ return Array.from(this.#sessions.keys()).map((id) => ({
1558
+ clientId: id,
1559
+ isActive: this.#sessions.get(id)?.isActive ?? false
1560
+ }));
1561
+ }
1562
+ listActive() {
1563
+ return this.list().filter((s) => s.isActive);
1564
+ }
1565
+ count() {
1566
+ return this.#sessions.size;
1567
+ }
1568
+ sendToClient(clientId, event, data, id = null) {
1569
+ const session = this.#sessions.get(clientId);
1570
+ if (!session || !session.isActive) {
1571
+ return false;
1402
1572
  }
1403
- return { path: result, name: result };
1404
- } catch (e) {
1405
- return { path: null, name: null };
1573
+ return session.sendEvent(event, data, id);
1406
1574
  }
1407
- }
1408
- async function getPackages(filterName = null) {
1409
- const cacheKey = filterName ? `packages:${filterName}` : "packages:all";
1410
- const cached = getCached(cacheKey);
1411
- if (cached) return cached;
1412
- if (!cad.connected) await cad.connect();
1413
- if (!cad.connected) return [];
1414
- try {
1415
- const code = `(mapcar 'cdr @::*local-pkgs*)`;
1416
- const result = await cad.sendCommandWithResult(code);
1417
- if (!result || result === "" || result === "nil") {
1418
- setCache(cacheKey, []);
1419
- return [];
1575
+ sendMessageToClient(clientId, data) {
1576
+ return this.sendToClient(clientId, SSE_EVENTS.MESSAGE, data);
1577
+ }
1578
+ sendErrorToClient(clientId, code, message, id = null) {
1579
+ return this.sendToClient(clientId, SSE_EVENTS.ERROR, { code, message }, id);
1580
+ }
1581
+ sendProgressToClient(clientId, current, total, message = "") {
1582
+ return this.sendToClient(clientId, SSE_EVENTS.PROGRESS, { current, total, message });
1583
+ }
1584
+ broadcast(event, data) {
1585
+ let successCount = 0;
1586
+ for (const [clientId, session] of this.#sessions) {
1587
+ if (session.isActive && session.sendEvent(event, data)) {
1588
+ successCount++;
1589
+ }
1590
+ }
1591
+ return successCount;
1592
+ }
1593
+ broadcastMessage(data) {
1594
+ return this.broadcast(SSE_EVENTS.MESSAGE, data);
1595
+ }
1596
+ broadcastError(code, message) {
1597
+ return this.broadcast(SSE_EVENTS.ERROR, { code, message });
1598
+ }
1599
+ on(event, handler) {
1600
+ if (!this.#eventHandlers.has(event)) {
1601
+ this.#eventHandlers.set(event, /* @__PURE__ */ new Set());
1602
+ }
1603
+ this.#eventHandlers.get(event).add(handler);
1604
+ }
1605
+ off(event, handler) {
1606
+ const handlers = this.#eventHandlers.get(event);
1607
+ if (handlers) {
1608
+ handlers.delete(handler);
1609
+ }
1610
+ }
1611
+ emit(event, data) {
1612
+ const handlers = this.#eventHandlers.get(event);
1613
+ if (handlers) {
1614
+ for (const handler of handlers) {
1615
+ try {
1616
+ handler(data);
1617
+ } catch (e) {
1618
+ }
1619
+ }
1620
+ }
1621
+ }
1622
+ cleanupInactive() {
1623
+ for (const [clientId, session] of this.#sessions) {
1624
+ if (!session.isActive) {
1625
+ session.close();
1626
+ this.#sessions.delete(clientId);
1627
+ }
1628
+ }
1629
+ }
1630
+ closeAll() {
1631
+ for (const [clientId, session] of this.#sessions) {
1632
+ persistSessionData(clientId, session.sessionData);
1633
+ session.close();
1634
+ }
1635
+ this.#sessions.clear();
1636
+ }
1637
+ };
1638
+
1639
+ // src/routes.js
1640
+ import express from "express";
1641
+ import { randomUUID as randomUUID4 } from "crypto";
1642
+ import iconv from "iconv-lite";
1643
+ import rateLimit from "express-rate-limit";
1644
+
1645
+ // src/mcp-server-state.js
1646
+ import { AsyncLocalStorage } from "async_hooks";
1647
+
1648
+ // src/session-transport.js
1649
+ import { randomUUID as randomUUID3 } from "crypto";
1650
+ var SESSION_TIMEOUT = 30 * 60 * 1e3;
1651
+ var SessionTransport = class {
1652
+ constructor(sessionId) {
1653
+ this.sessionId = sessionId || randomUUID3();
1654
+ this.onclose = null;
1655
+ this.onerror = null;
1656
+ this.onmessage = null;
1657
+ this._pendingRequest = null;
1658
+ this._started = false;
1659
+ this._closed = false;
1660
+ this._cleanupTimer = null;
1661
+ }
1662
+ async start() {
1663
+ if (this._started) {
1664
+ throw new Error("Transport already started");
1420
1665
  }
1421
- let raw = [];
1422
- try {
1423
- raw = JSON.parse(result);
1424
- } catch {
1425
- raw = parseLispList(result) || [];
1666
+ this._started = true;
1667
+ }
1668
+ async send(message) {
1669
+ if (this._closed) return;
1670
+ if (this._pendingRequest) {
1671
+ const { resolve, reject, timeout } = this._pendingRequest;
1672
+ clearTimeout(timeout);
1673
+ this._pendingRequest = null;
1674
+ resolve(message);
1426
1675
  }
1427
- if (!Array.isArray(raw)) raw = [];
1428
- const packages = raw.map((item) => {
1429
- if (Array.isArray(item)) {
1430
- const pkg = { name: "", version: "0.0.0", description: "", loaded: true };
1431
- for (const entry of item) {
1432
- if (entry && typeof entry === "object" && !Array.isArray(entry)) {
1433
- const [[key, val]] = Object.entries(entry);
1434
- const k = String(key);
1435
- const v = String(val);
1436
- if (k === ":NAME") pkg.name = v;
1437
- else if (k === ":VERSION") pkg.version = v;
1438
- else if (k === ":DESCRIPTION") pkg.description = v;
1439
- else if (k === ":FULL-NAME") pkg.fullName = v;
1440
- else if (k === ":AUTHOR") pkg.author = v;
1441
- else if (k === ":CATEGORY") pkg.category = v;
1442
- else if (k === ":URL") pkg.url = v;
1443
- }
1444
- }
1445
- return pkg;
1446
- }
1447
- if (typeof item === "string" || typeof item === "number") {
1448
- return { name: String(item), version: "0.0.0", loaded: true };
1449
- }
1450
- if (item && typeof item === "object") {
1451
- const pkg = { name: "", version: "0.0.0", description: "", loaded: true };
1452
- const nameVal = item.name || item.Name || item.NAME || item[":NAME"];
1453
- if (nameVal) pkg.name = String(nameVal);
1454
- const verVal = item.version || item.Version || item.VERSION || item[":VERSION"];
1455
- if (verVal) pkg.version = String(verVal);
1456
- const descVal = item.description || item.Description || item.DESCRIPTION || item[":DESCRIPTION"];
1457
- if (descVal) pkg.description = String(descVal);
1458
- if (item.fullName || item.full_name) pkg.fullName = String(item.fullName || item.full_name);
1459
- if (item.author || item.Author) pkg.author = String(item.author || item.Author);
1460
- if (item.category || item.Category) pkg.category = String(item.category || item.Category);
1461
- if (item.url || item.Url || item.URL) pkg.url = String(item.url || item.Url || item.URL);
1462
- return pkg;
1463
- }
1464
- return null;
1465
- }).filter(Boolean);
1466
- if (filterName) {
1467
- const filtered = packages.filter((p) => p.name === filterName);
1468
- setCache(cacheKey, filtered);
1469
- return filtered;
1676
+ }
1677
+ async close() {
1678
+ if (this._closed) return;
1679
+ this._closed = true;
1680
+ if (this._pendingRequest) {
1681
+ const { reject, timeout } = this._pendingRequest;
1682
+ clearTimeout(timeout);
1683
+ this._pendingRequest = null;
1684
+ reject(new Error("Transport closed"));
1470
1685
  }
1471
- setCache(cacheKey, packages);
1472
- return packages;
1473
- } catch (e) {
1474
- setCache(cacheKey, []);
1475
- return [];
1686
+ this._clearCleanupTimer();
1687
+ this.onclose?.();
1476
1688
  }
1477
- }
1478
- function getPlatforms() {
1479
- return CAD_PLATFORMS.map((name) => ({
1480
- name,
1481
- extensions: FILE_EXTENSIONS[name] || []
1482
- }));
1483
- }
1484
- async function getDwgList() {
1485
- const cacheKey = "cad:dwgs";
1486
- const cached = getCached(cacheKey);
1487
- if (cached) return cached;
1488
- if (!cad.connected) await cad.connect();
1489
- if (!cad.connected) return [];
1490
- try {
1491
- const code = `(progn
1492
- (vl-load-com)
1493
- (setq result nil)
1494
- (vlax-for doc (vla-get-Documents (vla-get-Application (vlax-get-acad-object)))
1495
- (setq result (cons (list (vla-get-Name doc) (vla-get-FullName doc)) result))
1496
- )
1497
- (reverse result)
1498
- )`;
1499
- const result = await cad.sendCommandWithResult(code);
1500
- if (!result || result === "" || result === "nil") {
1501
- setCache(cacheKey, []);
1502
- return [];
1689
+ async handleRequest(message, timeoutMs = 6e4) {
1690
+ if (this._closed) {
1691
+ throw new Error("Transport closed");
1503
1692
  }
1504
- let parsed = [];
1505
- try {
1506
- parsed = JSON.parse(result);
1507
- } catch (e) {
1508
- const listResult = parseLispList(result);
1509
- if (listResult) {
1510
- parsed = listResult;
1511
- }
1693
+ if (!message || !message.method) {
1694
+ throw new Error("Invalid message");
1512
1695
  }
1513
- if (!Array.isArray(parsed)) parsed = [];
1514
- const dwgList = parsed.map((item) => {
1515
- if (Array.isArray(item) && item.length >= 2) {
1516
- return { name: String(item[0]), path: String(item[1]) };
1517
- }
1696
+ if (!message.id) {
1697
+ this.onmessage?.(message);
1518
1698
  return null;
1519
- }).filter(Boolean);
1520
- setCache(cacheKey, dwgList);
1521
- return dwgList;
1522
- } catch (e) {
1523
- setCache(cacheKey, []);
1524
- return [];
1699
+ }
1700
+ return new Promise((resolve, reject) => {
1701
+ const timeout = setTimeout(() => {
1702
+ if (this._pendingRequest?.resolve === resolve) {
1703
+ this._pendingRequest = null;
1704
+ reject(new Error("Request timeout"));
1705
+ }
1706
+ }, timeoutMs);
1707
+ this._pendingRequest = { resolve, reject, timeout };
1708
+ this.onmessage?.(message);
1709
+ });
1525
1710
  }
1526
- }
1527
- function loadStandardsFile(filename) {
1528
- const filePath = path4.join(STANDARDS_DIR, filename);
1529
- try {
1530
- const raw = fs3.readFileSync(filePath, "utf-8");
1531
- return JSON.parse(raw);
1532
- } catch (e) {
1533
- log(`Error loading standards file ${filename}: ${e.message}`);
1534
- return null;
1711
+ resetCleanupTimer() {
1712
+ this._clearCleanupTimer();
1713
+ this._cleanupTimer = setTimeout(() => {
1714
+ this.close();
1715
+ }, SESSION_TIMEOUT);
1535
1716
  }
1536
- }
1537
- var standardsCache = /* @__PURE__ */ new Map();
1538
- function getCachedStandards(key) {
1539
- const entry = standardsCache.get(key);
1540
- if (entry && Date.now() - entry.timestamp < 3e4) return entry.data;
1541
- standardsCache.delete(key);
1542
- return null;
1543
- }
1544
- function setCachedStandards(key, data) {
1545
- standardsCache.set(key, { data, timestamp: Date.now() });
1546
- }
1547
- function getDraftingStandards() {
1548
- const cached = getCachedStandards("drafting");
1549
- if (cached) return cached;
1550
- const data = loadStandardsFile("drafting.json");
1551
- if (data) setCachedStandards("drafting", data);
1552
- return data || { error: "\u5236\u56FE\u89C4\u8303\u6587\u4EF6\u52A0\u8F7D\u5931\u8D25" };
1553
- }
1554
- function getCodingStandards() {
1555
- const cached = getCachedStandards("coding");
1556
- if (cached) return cached;
1557
- const data = loadStandardsFile("coding.json");
1558
- if (data) setCachedStandards("coding", data);
1559
- return data || { error: "\u7F16\u7801\u89C4\u8303\u6587\u4EF6\u52A0\u8F7D\u5931\u8D25" };
1560
- }
1717
+ _clearCleanupTimer() {
1718
+ if (this._cleanupTimer) {
1719
+ clearTimeout(this._cleanupTimer);
1720
+ this._cleanupTimer = null;
1721
+ }
1722
+ }
1723
+ };
1724
+
1725
+ // src/mcp-handlers.js
1726
+ import {
1727
+ ListToolsRequestSchema,
1728
+ CallToolRequestSchema,
1729
+ ListResourcesRequestSchema,
1730
+ ReadResourceRequestSchema,
1731
+ SubscribeRequestSchema,
1732
+ UnsubscribeRequestSchema,
1733
+ ListPromptsRequestSchema,
1734
+ GetPromptRequestSchema
1735
+ } from "@modelcontextprotocol/sdk/types.js";
1736
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
1561
1737
 
1562
1738
  // src/handlers/prompt-handlers.js
1563
1739
  import fs4 from "fs";
@@ -2329,574 +2505,453 @@ ${code}
2329
2505
  };
2330
2506
  }
2331
2507
 
2332
- // src/session-manager.js
2333
- var Session = class {
2334
- constructor(clientId) {
2335
- this.clientId = clientId;
2336
- this.subscriptions = /* @__PURE__ */ new Set();
2337
- this.createdAt = Date.now();
2338
- this.connected = false;
2339
- }
2508
+ // src/mcp-handlers.js
2509
+ var SERVER_CAPABILITIES = {
2510
+ tools: {},
2511
+ resources: { subscribe: true, listChanged: true },
2512
+ prompts: {}
2340
2513
  };
2341
- var SessionManager = class {
2342
- #sessions = /* @__PURE__ */ new Map();
2343
- createSession(clientId) {
2344
- let session = this.#sessions.get(clientId);
2345
- if (!session) {
2346
- session = new Session(clientId);
2347
- this.#sessions.set(clientId, session);
2348
- }
2349
- return session;
2350
- }
2351
- getSession(clientId) {
2352
- return this.#sessions.get(clientId) || null;
2353
- }
2354
- hasSession(clientId) {
2355
- return this.#sessions.has(clientId);
2356
- }
2357
- removeSession(clientId) {
2358
- return this.#sessions.delete(clientId);
2359
- }
2360
- subscribe(clientId, uri) {
2361
- const session = this.#sessions.get(clientId);
2362
- if (session) {
2363
- session.subscriptions.add(uri);
2364
- return true;
2514
+ function createMcpServer(handlers = {}) {
2515
+ const { tools: tools2 = [], handleToolCall: handleToolCall2 = () => ({ content: [] }) } = handlers;
2516
+ const server = new Server(
2517
+ { name: SERVER_NAME, version: SERVER_VERSION },
2518
+ { capabilities: SERVER_CAPABILITIES }
2519
+ );
2520
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: tools2 }));
2521
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2522
+ const { name, arguments: args } = request.params;
2523
+ return await handleToolCall2(name, args || {});
2524
+ });
2525
+ server.setRequestHandler(ListResourcesRequestSchema, async (request) => {
2526
+ const ctx = mcpContext.getStore();
2527
+ const sessionId = ctx?.sessionId;
2528
+ const subscribedUris = sessionId ? list(sessionId) : list();
2529
+ return {
2530
+ resources: await listResources(subscribedUris)
2531
+ };
2532
+ });
2533
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
2534
+ const { uri } = request.params;
2535
+ try {
2536
+ const data = await readResource(uri);
2537
+ return {
2538
+ contents: [{
2539
+ uri,
2540
+ mimeType: "application/json",
2541
+ text: JSON.stringify(data)
2542
+ }]
2543
+ };
2544
+ } catch (e) {
2545
+ return {
2546
+ contents: [],
2547
+ isError: true,
2548
+ error: { code: -32603, message: e.message }
2549
+ };
2365
2550
  }
2366
- return false;
2367
- }
2368
- unsubscribe(clientId, uri) {
2369
- const session = this.#sessions.get(clientId);
2370
- if (session) {
2371
- return session.subscriptions.delete(uri);
2551
+ });
2552
+ server.setRequestHandler(SubscribeRequestSchema, async (request) => {
2553
+ const { uri } = request.params;
2554
+ const ctx = mcpContext.getStore();
2555
+ subscribe(uri, ctx?.sessionId);
2556
+ return { success: true };
2557
+ });
2558
+ server.setRequestHandler(UnsubscribeRequestSchema, async (request) => {
2559
+ const { uri } = request.params;
2560
+ const ctx = mcpContext.getStore();
2561
+ unsubscribe(uri, ctx?.sessionId);
2562
+ return { success: true };
2563
+ });
2564
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({
2565
+ prompts: await listPrompts()
2566
+ }));
2567
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
2568
+ const { name, arguments: args } = request.params;
2569
+ return await getPrompt(name, args || {});
2570
+ });
2571
+ return server;
2572
+ }
2573
+
2574
+ // src/handlers/cad-handlers.js
2575
+ async function connectCad(platform = null) {
2576
+ log(`connect_cad called${platform ? ` (target: ${platform})` : ""}, cad.connected before: ${cad.connected}`);
2577
+ try {
2578
+ log("calling cad.connect()...");
2579
+ const connected = await cad.connect(platform);
2580
+ log("cad.connect() returned: " + connected + ", cad.connected: " + cad.connected);
2581
+ if (connected) {
2582
+ return { content: [{ type: "text", text: `\u5DF2\u8FDE\u63A5\u5230 ${cad.getPlatform()} ${cad.getVersion()}` }] };
2372
2583
  }
2373
- return false;
2584
+ } catch (e) {
2585
+ log("connect_cad error: " + e.message);
2374
2586
  }
2375
- getSubscriptions(clientId) {
2376
- const session = this.#sessions.get(clientId);
2377
- return session ? Array.from(session.subscriptions) : [];
2587
+ return { content: [{ type: "text", text: "\u672A\u80FD\u8FDE\u63A5\u6216\u542F\u52A8 CAD\uFF0C\u8BF7\u786E\u4FDD CAD \u5DF2\u5B89\u88C5" }], isError: true };
2588
+ }
2589
+ async function evalLisp(code, withResult = false, encoding = null) {
2590
+ if (!cad.connected) {
2591
+ await cad.connect();
2378
2592
  }
2379
- isSubscribed(clientId, uri) {
2380
- const session = this.#sessions.get(clientId);
2381
- return session ? session.subscriptions.has(uri) : false;
2593
+ if (!cad.connected) {
2594
+ return { content: [{ type: "text", text: "\u672A\u8FDE\u63A5 CAD" }], isError: true };
2382
2595
  }
2383
- getAllSubscribedUris() {
2384
- const all = /* @__PURE__ */ new Set();
2385
- for (const session of this.#sessions.values()) {
2386
- for (const uri of session.subscriptions) {
2387
- all.add(uri);
2596
+ const trimmed = (code || "").trim();
2597
+ if (!trimmed) return { content: [{ type: "text", text: "\u547D\u4EE4\u4E3A\u7A7A\uFF0C\u672A\u53D1\u9001" }] };
2598
+ try {
2599
+ if (withResult) {
2600
+ const result = await cad.sendCommandWithResult(trimmed, encoding);
2601
+ if (result !== null) {
2602
+ return { content: [{ type: "text", text: result }] };
2388
2603
  }
2604
+ return { content: [{ type: "text", text: "\u6267\u884C\u5931\u8D25\u6216\u65E0\u8FD4\u56DE\u503C" }], isError: true };
2605
+ } else {
2606
+ await cad.sendCommand(trimmed + "\n");
2607
+ return { content: [{ type: "text", text: `\u5DF2\u53D1\u9001\u547D\u4EE4` }] };
2389
2608
  }
2390
- return all;
2391
- }
2392
- list() {
2393
- return Array.from(this.#sessions.entries()).map(([id, s]) => ({
2394
- clientId: id,
2395
- subscriptions: Array.from(s.subscriptions),
2396
- createdAt: s.createdAt,
2397
- connected: s.connected
2398
- }));
2399
- }
2400
- count() {
2401
- return this.#sessions.size;
2402
- }
2403
- clear() {
2404
- this.#sessions.clear();
2405
- }
2406
- };
2407
-
2408
- // src/subscription-manager.js
2409
- var sessionManager = new SessionManager();
2410
- var _notifyFn = null;
2411
- function setNotify(fn) {
2412
- _notifyFn = fn;
2413
- }
2414
- function subscribe(uri, sessionId) {
2415
- if (sessionId) {
2416
- return sessionManager.subscribe(sessionId, uri);
2609
+ } catch (e) {
2610
+ return { content: [{ type: "text", text: `\u9519\u8BEF: ${e.message}` }], isError: true };
2417
2611
  }
2418
- return true;
2419
2612
  }
2420
- function unsubscribe(uri, sessionId) {
2421
- if (sessionId) {
2422
- return sessionManager.unsubscribe(sessionId, uri);
2613
+ async function getCadInfo2() {
2614
+ if (!cad.connected) {
2615
+ await cad.connect();
2423
2616
  }
2424
- return true;
2617
+ if (!cad.connected) return { content: [{ type: "text", text: "CAD \u672A\u8FDE\u63A5" }] };
2618
+ const info = await cad.getInfo();
2619
+ return { content: [{ type: "text", text: info }] };
2425
2620
  }
2426
- function list(sessionId) {
2427
- if (sessionId) {
2428
- return sessionManager.getSubscriptions(sessionId);
2429
- }
2430
- return Array.from(sessionManager.getAllSubscribedUris());
2621
+ async function atCommand(command) {
2622
+ if (!cad.connected) return { content: [{ type: "text", text: "\u8BF7\u5148\u8FDE\u63A5 CAD" }], isError: true };
2623
+ const trimmed = (command || "").trim();
2624
+ if (!trimmed) return { content: [{ type: "text", text: "\u547D\u4EE4\u4E3A\u7A7A\uFF0C\u672A\u53D1\u9001" }] };
2625
+ await cad.sendCommand(trimmed + "\n");
2626
+ return { content: [{ type: "text", text: "\u5DF2\u53D1\u9001\u547D\u4EE4" }] };
2431
2627
  }
2432
- function isSubscribed(uri, sessionId) {
2433
- if (sessionId) {
2434
- return sessionManager.isSubscribed(sessionId, uri);
2628
+ async function newDocument() {
2629
+ if (!cad.connected) {
2630
+ await cad.connect();
2435
2631
  }
2436
- return sessionManager.getAllSubscribedUris().has(uri);
2437
- }
2438
- function notify(uri, data) {
2439
- if (_notifyFn) {
2440
- _notifyFn(uri, data);
2632
+ if (!cad.connected) return { content: [{ type: "text", text: "\u672A\u8FDE\u63A5 CAD" }], isError: true };
2633
+ const result = await cad.newDoc();
2634
+ if (result) {
2635
+ return { content: [{ type: "text", text: "\u5DF2\u5728 CAD \u4E2D\u65B0\u5EFA\u7A7A\u767D\u6587\u6863" }] };
2441
2636
  }
2637
+ return { content: [{ type: "text", text: "\u65B0\u5EFA\u6587\u6863\u5931\u8D25" }], isError: true };
2442
2638
  }
2443
-
2444
- // src/sse-session.js
2445
- import { randomUUID as randomUUID3 } from "crypto";
2446
- var DEFAULT_HEARTBEAT_INTERVAL = 3e4;
2447
- var DEFAULT_RETRY_INTERVAL = 3e4;
2448
- var MAX_SENT_MESSAGES = 1e3;
2449
- var SSE_EVENTS = {
2450
- MESSAGE: "message",
2451
- ENDPOINT: "endpoint",
2452
- PING: "ping",
2453
- ERROR: "error",
2454
- PROGRESS: "progress",
2455
- CAPABILITIES: "capabilities"
2456
- };
2457
- var SseSession = class {
2458
- #res;
2459
- #clientId;
2460
- #active = true;
2461
- #heartbeatTimer = null;
2462
- #lastEventId = 0;
2463
- #heartbeatInterval;
2464
- #retryInterval;
2465
- #sessionData = {};
2466
- #sentMessages = [];
2467
- #backpressureBuffer = [];
2468
- #drainHandler = null;
2469
- constructor(res, options = {}) {
2470
- this.#res = res;
2471
- this.#clientId = options.clientId || randomUUID3();
2472
- this.#heartbeatInterval = options.heartbeatInterval || DEFAULT_HEARTBEAT_INTERVAL;
2473
- this.#retryInterval = options.retryInterval || DEFAULT_RETRY_INTERVAL;
2474
- this.#sessionData = options.sessionData || {};
2475
- if (options.resumeFromId && options.resumeFromId > 0) {
2476
- this.#lastEventId = options.resumeFromId;
2477
- }
2478
- this._setupSseHeaders();
2479
- this._sendRetry();
2480
- this._setupDrain();
2481
- }
2482
- get clientId() {
2483
- return this.#clientId;
2484
- }
2485
- get isActive() {
2486
- return this.#active;
2487
- }
2488
- get lastEventId() {
2489
- return this.#lastEventId;
2490
- }
2491
- get sessionData() {
2492
- return this.#sessionData;
2493
- }
2494
- get sentMessages() {
2495
- return [...this.#sentMessages];
2496
- }
2497
- getMessagesSince(eventId) {
2498
- return this.#sentMessages.filter((m) => m.id > eventId);
2499
- }
2500
- setSessionData(key, value) {
2501
- this.#sessionData[key] = value;
2502
- }
2503
- getSessionData(key) {
2504
- return this.#sessionData[key];
2505
- }
2506
- get retryInterval() {
2507
- return this.#retryInterval;
2639
+ async function bringToFront() {
2640
+ if (!cad.connected) {
2641
+ await cad.connect();
2508
2642
  }
2509
- setRetryInterval(ms) {
2510
- this.#retryInterval = ms;
2643
+ if (!cad.connected) return { content: [{ type: "text", text: "\u672A\u8FDE\u63A5 CAD" }], isError: true };
2644
+ const result = await cad.bringToFront();
2645
+ if (result) {
2646
+ return { content: [{ type: "text", text: "\u5DF2\u5C06 CAD \u7A97\u53E3\u5207\u6362\u5230\u524D\u53F0" }] };
2511
2647
  }
2512
- _sendRetry() {
2513
- this._write(`retry: ${this.#retryInterval}
2514
-
2515
- `);
2648
+ return { content: [{ type: "text", text: "\u5207\u6362\u524D\u53F0\u5931\u8D25" }], isError: true };
2649
+ }
2650
+ async function installAtlisp() {
2651
+ if (!cad.connected) {
2652
+ await cad.connect();
2516
2653
  }
2517
- _setupSseHeaders() {
2518
- this.#res.setHeader("Content-Type", "text/event-stream");
2519
- this.#res.setHeader("Cache-Control", "no-cache");
2520
- this.#res.setHeader("Connection", "keep-alive");
2521
- this.#res.setHeader("Transfer-Encoding", "chunked");
2522
- this.#res.setHeader("X-Accel-Buffering", "no");
2523
- if (typeof this.#res.flush !== "function") {
2524
- this.#res.flush = () => {
2525
- };
2526
- }
2654
+ if (!cad.connected) return { content: [{ type: "text", text: "\u672A\u8FDE\u63A5 CAD" }], isError: true };
2655
+ 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))))`;
2656
+ await cad.sendCommand(installCode + "\n");
2657
+ return { content: [{ type: "text", text: "\u5DF2\u53D1\u9001 @lisp \u5B89\u88C5\u4EE3\u7801\u5230 CAD" }] };
2658
+ }
2659
+ async function listFunctionsInCad() {
2660
+ if (!cad.connected) {
2661
+ await cad.connect();
2527
2662
  }
2528
- _formatEvent(event, data, id = null) {
2529
- const lines = [];
2530
- if (id !== null) {
2531
- lines.push(`id: ${id}`);
2532
- }
2533
- lines.push(`event: ${event}`);
2534
- if (data !== null && data !== void 0) {
2535
- const jsonStr = JSON.stringify(data);
2536
- lines.push(`data: ${jsonStr}`);
2537
- } else {
2538
- lines.push("data:");
2539
- }
2540
- return lines.join("\n") + "\n\n";
2663
+ if (!cad.connected) return { content: [{ type: "text", text: "\u672A\u8FDE\u63A5 CAD" }], isError: true };
2664
+ try {
2665
+ const result = await cad.sendCommandWithResult("(atoms-family 0)", null);
2666
+ if (result) return { content: [{ type: "text", text: result }] };
2667
+ return { content: [{ type: "text", text: "CAD \u51FD\u6570\u5217\u8868\u4E3A\u7A7A" }] };
2668
+ } catch (e) {
2669
+ return { content: [{ type: "text", text: `\u9519\u8BEF: ${e.message}` }], isError: true };
2541
2670
  }
2542
- _setupDrain() {
2543
- if (typeof this.#res.on !== "function") return;
2544
- this.#drainHandler = () => {
2545
- if (!this.#active) return;
2546
- if (this.#backpressureBuffer.length > 0) {
2547
- this._drainBuffer();
2548
- }
2549
- };
2550
- this.#res.on("drain", this.#drainHandler);
2671
+ }
2672
+ async function getSystemStatus() {
2673
+ const status = {
2674
+ mcp: { version: process.env.npm_package_version || "unknown" },
2675
+ worker: { alive: false },
2676
+ cad: { connected: false, platform: null, version: null, busy: false, hasDoc: false },
2677
+ lisp: { available: false, testResult: null, error: null },
2678
+ packages: null
2679
+ };
2680
+ try {
2681
+ status.worker.alive = await cad.ping();
2682
+ } catch {
2683
+ status.worker.alive = false;
2551
2684
  }
2552
- _drainBuffer() {
2553
- const batch = this.#backpressureBuffer.splice(0, 50);
2554
- for (const item of batch) {
2555
- const ok = this.#res.write(item);
2556
- if (!ok) {
2557
- this.#backpressureBuffer.unshift(...batch.slice(batch.indexOf(item)));
2558
- return;
2559
- }
2685
+ status.cad.connected = cad.connected;
2686
+ if (cad.connected) {
2687
+ status.cad.platform = cad.getPlatform();
2688
+ status.cad.version = cad.getVersion();
2689
+ try {
2690
+ status.cad.busy = await cad.isBusy();
2691
+ } catch {
2560
2692
  }
2561
- this._flush();
2562
- if (this.#backpressureBuffer.length > 0) {
2563
- this._drainBuffer();
2693
+ try {
2694
+ const doc = await cad.hasDoc();
2695
+ status.cad.hasDoc = doc.hasDoc;
2696
+ } catch {
2564
2697
  }
2565
- }
2566
- _write(content) {
2567
- if (!this.#active) return false;
2568
2698
  try {
2569
- if (this.#backpressureBuffer.length > 0) {
2570
- this.#backpressureBuffer.push(content);
2571
- return true;
2572
- }
2573
- const result = this.#res.write(content);
2574
- if (typeof this.#res.flush === "function") {
2575
- this.#res.flush();
2576
- }
2577
- if (result === false) {
2578
- this.#backpressureBuffer.push(content);
2579
- return true;
2699
+ const r = await cad.sendCommandWithResult("(+ 1 2)");
2700
+ if (r !== null && r !== void 0) {
2701
+ status.lisp.available = true;
2702
+ status.lisp.testResult = String(r).trim();
2580
2703
  }
2581
- return true;
2582
2704
  } catch (e) {
2583
- this.#active = false;
2584
- this.close();
2585
- return false;
2705
+ status.lisp.error = e.message;
2586
2706
  }
2587
- }
2588
- _flush() {
2589
2707
  try {
2590
- if (typeof this.#res.flush === "function") {
2591
- this.#res.flush();
2708
+ const pkgResult = await cad.sendCommandWithResult("(@::package-list-in-local)");
2709
+ if (pkgResult && pkgResult !== "nil") {
2710
+ status.packages = pkgResult;
2592
2711
  }
2593
- } catch (e) {
2594
- }
2595
- }
2596
- flushBackpressure() {
2597
- if (this.#backpressureBuffer.length > 0) {
2598
- this._drainBuffer();
2599
- }
2600
- }
2601
- sendEvent(event, data, id = null) {
2602
- this.#lastEventId++;
2603
- const eventId = id !== null ? id : this.#lastEventId;
2604
- const formatted = this._formatEvent(event, data, eventId);
2605
- this.#sentMessages.push({ event, data, id: eventId, timestamp: Date.now() });
2606
- if (this.#sentMessages.length > MAX_SENT_MESSAGES) {
2607
- this.#sentMessages.splice(0, this.#sentMessages.length - MAX_SENT_MESSAGES);
2712
+ } catch {
2608
2713
  }
2609
- this._write(formatted);
2610
- this._flush();
2611
- return true;
2612
2714
  }
2613
- sendMessage(data) {
2614
- return this.sendEvent(SSE_EVENTS.MESSAGE, data);
2715
+ return { content: [{ type: "text", text: JSON.stringify(status, null, 2) }] };
2716
+ }
2717
+ async function initAtlisp() {
2718
+ if (!cad.connected) {
2719
+ await cad.connect();
2615
2720
  }
2616
- sendEndpoint(path8) {
2617
- return this.sendEvent(SSE_EVENTS.ENDPOINT, path8);
2721
+ if (!cad.connected) return { content: [{ type: "text", text: "\u672A\u8FDE\u63A5 CAD" }], isError: true };
2722
+ const code = `(if (null @::load-module)
2723
+ (progn
2724
+ (vl-load-com)
2725
+ (setq s strcat h"http"o(vlax-create-object (s"win"h".win"h"request.5.1"))
2726
+ v vlax-invoke e eval r read)(v o'open "get" (s h"://""atlisp.""cn/cloud"):vlax-true)
2727
+ (v o'send)
2728
+ (v o'WaitforResponse 1000)
2729
+ (e(r(vlax-get o'ResponseText)))))`;
2730
+ try {
2731
+ await cad.sendCommand(code + "\n");
2732
+ return { content: [{ type: "text", text: "\u5DF2\u53D1\u9001 @lisp \u51FD\u6570\u5E93\u52A0\u8F7D\u4EE3\u7801\u5230 CAD" }] };
2733
+ } catch (e) {
2734
+ return { content: [{ type: "text", text: `\u9519\u8BEF: ${e.message}` }], isError: true };
2618
2735
  }
2619
- sendError(code, message, id = null) {
2620
- return this.sendEvent(SSE_EVENTS.ERROR, { code, message }, id);
2736
+ }
2737
+
2738
+ // src/handlers/package-handlers.js
2739
+ var PACKAGES_LIST_URL = process.env.PACKAGES_LIST_URL || "http://s3.atlisp.cn/packages-list?fmt=json";
2740
+ var cachedPackages = null;
2741
+ var cachedAt = null;
2742
+ var CACHE_TTL2 = 10 * 60 * 1e3;
2743
+ async function fetchPackages() {
2744
+ const now = Date.now();
2745
+ if (cachedPackages && cachedAt && now - cachedAt < CACHE_TTL2) {
2746
+ return cachedPackages;
2621
2747
  }
2622
- sendPing() {
2623
- return this.sendEvent(SSE_EVENTS.PING, { timestamp: Date.now() });
2748
+ try {
2749
+ const response = await fetch(PACKAGES_LIST_URL, { headers: { "User-Agent": "atlisp-mcp" } });
2750
+ if (response.ok) {
2751
+ const data = await response.json();
2752
+ cachedPackages = data;
2753
+ cachedAt = now;
2754
+ return data;
2755
+ }
2756
+ } catch (e) {
2624
2757
  }
2625
- sendProgress(current, total, message = "") {
2626
- return this.sendEvent(SSE_EVENTS.PROGRESS, { current, total, message });
2758
+ return cachedPackages || MOCK_PACKAGES;
2759
+ }
2760
+ function flattenPackages(data) {
2761
+ if (Array.isArray(data)) return data;
2762
+ if (data && Array.isArray(data.packages)) return data.packages;
2763
+ return [];
2764
+ }
2765
+ async function listPackages() {
2766
+ if (!cad.connected) await cad.connect();
2767
+ if (!cad.connected) {
2768
+ const data2 = await fetchPackages();
2769
+ const items2 = flattenPackages(data2);
2770
+ const text2 = items2.length ? items2.map((p) => `${p.name} v${p.version || ""} - ${p.description || ""}`).join("\n") : "\u65E0\u53EF\u7528\u5305";
2771
+ return { content: [{ type: "text", text: text2 }] };
2627
2772
  }
2628
- sendCapabilities(capabilities) {
2629
- return this.sendEvent(SSE_EVENTS.CAPABILITIES, capabilities);
2773
+ const result = await cad.sendCommandWithResult("(@::package-list-in-local)");
2774
+ if (result && result !== "nil") return { content: [{ type: "text", text: result }] };
2775
+ const data = await fetchPackages();
2776
+ const items = flattenPackages(data);
2777
+ const text = items.length ? `\u4ECE\u6CE8\u518C\u8868\u83B7\u53D6\u7684 @lisp \u5305:
2778
+ ${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");
2779
+ return { content: [{ type: "text", text: `\u5DF2\u5B89\u88C5\u7684 @lisp \u5305:
2780
+ ${text}` }] };
2781
+ }
2782
+ async function searchPackages(query) {
2783
+ const data = await fetchPackages();
2784
+ if (!data) {
2785
+ return { content: [{ type: "text", text: "\u65E0\u6CD5\u83B7\u53D6\u5305\u5217\u8868" }], isError: true };
2630
2786
  }
2631
- sendResponse(jsonrpcResponse, id = null) {
2632
- const eventId = id !== null ? id : this.#lastEventId;
2633
- return this.sendEvent(SSE_EVENTS.MESSAGE, jsonrpcResponse, eventId);
2787
+ const items = flattenPackages(data);
2788
+ const results = [];
2789
+ if (query) {
2790
+ const q = query.toLowerCase();
2791
+ for (const p of items) {
2792
+ const name = (p.name || "").toLowerCase();
2793
+ const desc = (p.description || "").toLowerCase();
2794
+ if (name.includes(q) || desc.includes(q)) results.push(p);
2795
+ }
2796
+ } else {
2797
+ for (const p of items) results.push(p);
2634
2798
  }
2635
- startHeartbeat(interval = null) {
2636
- this.stopHeartbeat();
2637
- const ms = interval || this.#heartbeatInterval;
2638
- this.#heartbeatTimer = setInterval(() => {
2639
- if (!this.#active) {
2640
- this.stopHeartbeat();
2641
- return;
2642
- }
2643
- if (!this.sendPing()) {
2644
- this.stopHeartbeat();
2645
- this.close();
2646
- }
2647
- }, ms);
2799
+ if (results.length === 0) {
2800
+ return { content: [{ type: "text", text: `\u672A\u627E\u5230\u5305\u542B "${query}" \u7684\u5305` }] };
2648
2801
  }
2649
- stopHeartbeat() {
2650
- if (this.#heartbeatTimer) {
2651
- clearInterval(this.#heartbeatTimer);
2652
- this.#heartbeatTimer = null;
2653
- }
2802
+ return { content: [{ type: "text", text: `\u641C\u7D22\u7ED3\u679C (${results.length}):
2803
+ ${results.map((p) => `${p.name} v${p.version || ""} - ${p.description || ""}`).join("\n")}` }] };
2804
+ }
2805
+ async function installPackage(packageName) {
2806
+ const data = await fetchPackages();
2807
+ const items = flattenPackages(data);
2808
+ const pkg = items.find((p) => p.name === packageName);
2809
+ if (!pkg) {
2810
+ return { content: [{ type: "text", text: `\u9519\u8BEF: \u5305 "${packageName}" \u4E0D\u5B58\u5728` }], isError: true };
2654
2811
  }
2655
- close() {
2656
- this.#active = false;
2657
- this.stopHeartbeat();
2658
- if (this.#drainHandler) {
2659
- this.#res.removeListener("drain", this.#drainHandler);
2660
- this.#drainHandler = null;
2661
- }
2662
- this.#backpressureBuffer = [];
2663
- try {
2664
- this.#res.end();
2665
- } catch (e) {
2666
- }
2812
+ if (cad.connected) {
2813
+ await cad.sendCommand(`(@::load-module '${packageName})
2814
+ `);
2667
2815
  }
2668
- };
2816
+ return { content: [{ type: "text", text: `\u5305 "${packageName}" \u5B89\u88C5\u547D\u4EE4\u5DF2\u53D1\u9001\uFF01` }] };
2817
+ }
2669
2818
 
2670
- // src/sse-session-manager.js
2819
+ // src/handlers/function-handlers.js
2671
2820
  import fs5 from "fs";
2672
2821
  import path6 from "path";
2673
2822
  import os3 from "os";
2674
- var SESSION_STORE_DIR = path6.join(os3.homedir(), ".atlisp", "sessions");
2675
- var SESSION_STORE_TTL = 24 * 60 * 60 * 1e3;
2676
- function getSessionStorePath(clientId) {
2677
- return path6.join(SESSION_STORE_DIR, `${clientId}.json`);
2678
- }
2679
- function persistSessionData(clientId, sessionData) {
2823
+ var FUNCTIONS_DIR = process.env.FUNCTIONS_DIR || "";
2824
+ var FUNCTIONS_URL = process.env.FUNCTIONS_URL || "http://s3.atlisp.cn/json/functions.json";
2825
+ var CACHE_DIR = path6.join(os3.homedir(), ".atlisp", "cache");
2826
+ var FUNCLIB_CACHE_FILE = path6.join(CACHE_DIR, "functions.json");
2827
+ var FUNCLIB_CACHE_TTL = 24 * 60 * 60 * 1e3;
2828
+ function readCacheFromDisk() {
2680
2829
  try {
2681
- fs5.mkdirSync(SESSION_STORE_DIR, { recursive: true });
2682
- const store = {
2683
- clientId,
2684
- sessionData,
2685
- savedAt: Date.now()
2686
- };
2687
- fs5.writeFileSync(getSessionStorePath(clientId), JSON.stringify(store), "utf-8");
2830
+ const stat = fs5.statSync(FUNCLIB_CACHE_FILE);
2831
+ if (Date.now() - stat.mtimeMs < FUNCLIB_CACHE_TTL) {
2832
+ const raw = fs5.readFileSync(FUNCLIB_CACHE_FILE, "utf-8");
2833
+ return JSON.parse(raw);
2834
+ }
2835
+ } catch {
2836
+ }
2837
+ return null;
2838
+ }
2839
+ function writeCacheToDisk(data) {
2840
+ try {
2841
+ fs5.mkdirSync(CACHE_DIR, { recursive: true });
2842
+ fs5.writeFileSync(FUNCLIB_CACHE_FILE, JSON.stringify(data), "utf-8");
2688
2843
  } catch (e) {
2689
- console.error("Failed to persist session:", e.message);
2844
+ log(`function-handlers writeCache error: ${e.message}`);
2690
2845
  }
2691
2846
  }
2692
- function loadPersistedSessionData(clientId) {
2847
+ async function fetchFunctions() {
2848
+ const diskCache = readCacheFromDisk();
2849
+ if (diskCache) return diskCache;
2693
2850
  try {
2694
- const filePath = getSessionStorePath(clientId);
2695
- if (!fs5.existsSync(filePath)) return null;
2696
- const raw = fs5.readFileSync(filePath, "utf-8");
2697
- const store = JSON.parse(raw);
2698
- if (Date.now() - store.savedAt > SESSION_STORE_TTL) {
2699
- fs5.unlinkSync(filePath);
2700
- return null;
2851
+ const response = await fetch(FUNCTIONS_URL, { headers: { "User-Agent": "atlisp-mcp" } });
2852
+ if (response.ok) {
2853
+ const data = await response.json();
2854
+ writeCacheToDisk(data);
2855
+ return data;
2701
2856
  }
2702
- return store.sessionData || {};
2703
2857
  } catch (e) {
2704
- return null;
2858
+ log(`function-handlers fetch error: ${e.message}`);
2705
2859
  }
2860
+ return diskCache;
2706
2861
  }
2707
- var SseSessionManager = class {
2708
- #sessions = /* @__PURE__ */ new Map();
2709
- #eventHandlers = /* @__PURE__ */ new Map();
2710
- constructor() {
2711
- this.#sessions = /* @__PURE__ */ new Map();
2712
- this.#eventHandlers = /* @__PURE__ */ new Map();
2713
- }
2714
- add(clientId, session) {
2715
- if (!(session instanceof SseSession)) {
2716
- throw new Error("session must be an SseSession instance");
2862
+ async function getFunctionUsage(funcName, packageName) {
2863
+ try {
2864
+ let results = [];
2865
+ const data = await fetchFunctions();
2866
+ if (data) {
2867
+ const funcs = Object.values(data.all_functions || {});
2868
+ const func = funcs.find((f2) => f2.name === funcName || f2.name === `${packageName}:${funcName}`);
2869
+ if (func) results.push(func);
2717
2870
  }
2718
- const persisted = loadPersistedSessionData(clientId);
2719
- if (persisted) {
2720
- for (const [key, value] of Object.entries(persisted)) {
2721
- session.setSessionData(key, value);
2871
+ if (results.length === 0 && FUNCTIONS_DIR) {
2872
+ try {
2873
+ const files = fs5.readdirSync(FUNCTIONS_DIR).filter((f2) => f2.endsWith(".json"));
2874
+ for (const file of files) {
2875
+ const raw = fs5.readFileSync(path6.join(FUNCTIONS_DIR, file), "utf-8");
2876
+ const data2 = JSON.parse(raw);
2877
+ const func = data2.functions?.find((f2) => f2.name === funcName);
2878
+ if (func) results.push(func);
2879
+ }
2880
+ } catch {
2722
2881
  }
2723
2882
  }
2724
- this.#sessions.set(clientId, session);
2725
- session.startHeartbeat();
2726
- }
2727
- remove(clientId) {
2728
- const session = this.#sessions.get(clientId);
2729
- if (session) {
2730
- persistSessionData(clientId, session.sessionData);
2731
- session.close();
2732
- this.#sessions.delete(clientId);
2733
- return true;
2734
- }
2735
- return false;
2736
- }
2737
- get(clientId) {
2738
- return this.#sessions.get(clientId) || null;
2739
- }
2740
- has(clientId) {
2741
- return this.#sessions.has(clientId);
2742
- }
2743
- list() {
2744
- return Array.from(this.#sessions.keys()).map((id) => ({
2745
- clientId: id,
2746
- isActive: this.#sessions.get(id)?.isActive ?? false
2747
- }));
2748
- }
2749
- listActive() {
2750
- return this.list().filter((s) => s.isActive);
2751
- }
2752
- count() {
2753
- return this.#sessions.size;
2754
- }
2755
- sendToClient(clientId, event, data, id = null) {
2756
- const session = this.#sessions.get(clientId);
2757
- if (!session || !session.isActive) {
2758
- return false;
2883
+ if (results.length === 0) {
2884
+ return { content: [{ type: "text", text: `\u672A\u627E\u5230\u51FD\u6570: ${funcName}` }], isError: true };
2759
2885
  }
2760
- return session.sendEvent(event, data, id);
2761
- }
2762
- sendMessageToClient(clientId, data) {
2763
- return this.sendToClient(clientId, SSE_EVENTS.MESSAGE, data);
2764
- }
2765
- sendErrorToClient(clientId, code, message, id = null) {
2766
- return this.sendToClient(clientId, SSE_EVENTS.ERROR, { code, message }, id);
2767
- }
2768
- sendProgressToClient(clientId, current, total, message = "") {
2769
- return this.sendToClient(clientId, SSE_EVENTS.PROGRESS, { current, total, message });
2886
+ const f = results[0];
2887
+ const text = `\u51FD\u6570: ${f.name}
2888
+ \u63CF\u8FF0: ${f.description || "-"}
2889
+ \u53C2\u6570: ${f.params?.join(", ") || "-"}
2890
+ \u8FD4\u56DE\u503C: ${f.return || "-"}
2891
+ \u793A\u4F8B: ${f.usage || "-"}`;
2892
+ return { content: [{ type: "text", text }] };
2893
+ } catch (e) {
2894
+ return { content: [{ type: "text", text: `\u9519\u8BEF: ${e.message}` }], isError: true };
2770
2895
  }
2771
- broadcast(event, data) {
2772
- let successCount = 0;
2773
- for (const [clientId, session] of this.#sessions) {
2774
- if (session.isActive && session.sendEvent(event, data)) {
2775
- successCount++;
2896
+ }
2897
+ async function listSymbols(packageName) {
2898
+ try {
2899
+ let allFuncs = [];
2900
+ const data = await fetchFunctions();
2901
+ if (data) {
2902
+ const funcs = Object.values(data.all_functions || {});
2903
+ if (packageName) {
2904
+ allFuncs = funcs.filter((f) => f.name.startsWith(`${packageName}:`));
2905
+ } else {
2906
+ allFuncs = funcs.map((f) => `${f.name}: ${f.description || "-"}`);
2776
2907
  }
2777
2908
  }
2778
- return successCount;
2779
- }
2780
- broadcastMessage(data) {
2781
- return this.broadcast(SSE_EVENTS.MESSAGE, data);
2782
- }
2783
- broadcastError(code, message) {
2784
- return this.broadcast(SSE_EVENTS.ERROR, { code, message });
2785
- }
2786
- on(event, handler) {
2787
- if (!this.#eventHandlers.has(event)) {
2788
- this.#eventHandlers.set(event, /* @__PURE__ */ new Set());
2789
- }
2790
- this.#eventHandlers.get(event).add(handler);
2791
- }
2792
- off(event, handler) {
2793
- const handlers = this.#eventHandlers.get(event);
2794
- if (handlers) {
2795
- handlers.delete(handler);
2796
- }
2797
- }
2798
- emit(event, data) {
2799
- const handlers = this.#eventHandlers.get(event);
2800
- if (handlers) {
2801
- for (const handler of handlers) {
2802
- try {
2803
- handler(data);
2804
- } catch (e) {
2909
+ if (allFuncs.length === 0 && FUNCTIONS_DIR) {
2910
+ try {
2911
+ const files = fs5.readdirSync(FUNCTIONS_DIR).filter((f) => f.endsWith(".json"));
2912
+ for (const file of files) {
2913
+ const raw = fs5.readFileSync(path6.join(FUNCTIONS_DIR, file), "utf-8");
2914
+ const data2 = JSON.parse(raw);
2915
+ const funcs = Object.values(data2.all_functions || {});
2916
+ if (packageName) {
2917
+ allFuncs = funcs.filter((f) => f.name.startsWith(`${packageName}:`));
2918
+ } else {
2919
+ for (const f of funcs) {
2920
+ allFuncs.push(`${f.name}: ${f.description || "-"}`);
2921
+ }
2922
+ }
2805
2923
  }
2924
+ } catch (err) {
2925
+ log(`listSymbols readDir error: ${err.message}`);
2806
2926
  }
2807
2927
  }
2808
- }
2809
- cleanupInactive() {
2810
- for (const [clientId, session] of this.#sessions) {
2811
- if (!session.isActive) {
2812
- session.close();
2813
- this.#sessions.delete(clientId);
2814
- }
2928
+ if (allFuncs.length === 0) {
2929
+ return await listFunctionsInCad();
2815
2930
  }
2816
- }
2817
- closeAll() {
2818
- for (const [clientId, session] of this.#sessions) {
2819
- persistSessionData(clientId, session.sessionData);
2820
- session.close();
2931
+ if (packageName) {
2932
+ return { content: [{ type: "text", text: `${packageName} \u5305\u51FD\u6570 (${allFuncs.length}):
2933
+ ${allFuncs.map((f) => f.name).join("\n")}` }] };
2821
2934
  }
2822
- this.#sessions.clear();
2935
+ return { content: [{ type: "text", text: allFuncs.join("\n") }] };
2936
+ } catch (e) {
2937
+ return { content: [{ type: "text", text: `\u9519\u8BEF: ${e.message}` }], isError: true };
2823
2938
  }
2824
- };
2939
+ }
2825
2940
 
2826
- // src/atlisp-mcp.js
2827
- var __dirname4 = path7.dirname(fileURLToPath4(import.meta.url));
2828
- var require2 = createRequire(import.meta.url);
2829
- var { version } = require2(path7.join(__dirname4, "..", "package.json"));
2941
+ // src/function-lib.js
2942
+ import path7 from "path";
2943
+ import fs6 from "fs";
2944
+ import os4 from "os";
2830
2945
  var CACHE_DIR2 = path7.join(os4.homedir(), ".atlisp", "cache");
2831
2946
  var FUNCLIB_CACHE_FILE2 = path7.join(CACHE_DIR2, "functions.json");
2832
- var FUNCLIB_CACHE_TTL2 = 24 * 60 * 60 * 1e3;
2833
- var isMcpScript = process.argv[1]?.includes("atlisp-mcp");
2834
- if (isMcpScript) {
2835
- const args = process.argv.slice(2);
2836
- for (let i = 0; i < args.length; i++) {
2837
- if (args[i] === "--version" || args[i] === "-v") {
2838
- console.log(`atlisp-mcp v${version}`);
2839
- process.exit(0);
2840
- } else if (args[i] === "--help" || args[i] === "-h") {
2841
- console.log(`
2842
- @lisp MCP Server v${version}
2843
-
2844
- Usage: atlisp-mcp [options]
2845
-
2846
- Options:
2847
- -v, --version Show version number
2848
- --transport <type> Transport type: http or stdio (default: http)
2849
- --port <port> HTTP port (default: 8110)
2850
- --host <host> HTTP host (default: 0.0.0.0)
2851
- --stdio Shorthand for --transport stdio
2852
- -h, --help Show this help message
2853
-
2854
- Examples:
2855
- atlisp-mcp # Start HTTP server on port 8110
2856
- atlisp-mcp --port 3000 # Start HTTP server on port 3000
2857
- atlisp-mcp --stdio # Start in stdio mode for MCP clients
2858
- atlisp-mcp --transport stdio # Same as --stdio
2859
- `);
2860
- process.exit(0);
2861
- } else if (args[i] === "--transport" && args[i + 1]) {
2862
- process.env.TRANSPORT = args[i + 1];
2863
- i++;
2864
- } else if (args[i] === "--stdio") {
2865
- process.env.TRANSPORT = "stdio";
2866
- } else if (args[i] === "--port" && args[i + 1]) {
2867
- process.env.PORT = args[i + 1];
2868
- i++;
2869
- } else if (args[i] === "--host" && args[i + 1]) {
2870
- process.env.HOST = args[i + 1];
2871
- i++;
2872
- }
2873
- }
2874
- }
2875
- var { decode: charsetDecode, encodingExists } = iconv;
2876
- var mcpContext = new AsyncLocalStorage();
2877
- var sseSessionManager = new SseSessionManager();
2878
- var SERVER_CAPABILITIES = {
2879
- tools: {},
2880
- resources: { subscribe: true, listChanged: true },
2881
- prompts: {}
2882
- };
2883
- var { port, host, transport, apiKey, rateLimitWindow, rateLimitMax } = config_default;
2884
- if (apiKey) log("API Key authentication enabled");
2885
- var mcpLimiter = rateLimit({
2886
- windowMs: rateLimitWindow,
2887
- max: rateLimitMax,
2888
- standardHeaders: true,
2889
- legacyHeaders: false,
2890
- message: { error: "\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5" }
2891
- });
2892
- var FUNCTION_LIB_URL = process.env.FUNCTION_LIB_URL || "http://s3.atlisp.cn/json/functions.json";
2947
+ var CACHE_TTL3 = config_default.functionLibCacheTtl || 5 * 60 * 1e3;
2948
+ var FUNCTION_LIB_URL = config_default.functionLibUrl || "http://s3.atlisp.cn/json/functions.json";
2893
2949
  var cachedFunctionLib = null;
2894
2950
  var cachedAt2 = null;
2895
- var CACHE_TTL3 = 5 * 60 * 1e3;
2896
2951
  async function readCacheFromDisk2() {
2897
2952
  try {
2898
2953
  const stat = fs6.statSync(FUNCLIB_CACHE_FILE2);
2899
- if (Date.now() - stat.mtimeMs < FUNCLIB_CACHE_TTL2) {
2954
+ if (Date.now() - stat.mtimeMs < CACHE_TTL3) {
2900
2955
  const raw = fs6.readFileSync(FUNCLIB_CACHE_FILE2, "utf-8");
2901
2956
  return JSON.parse(raw);
2902
2957
  }
@@ -2909,7 +2964,7 @@ function writeCacheToDisk2(data) {
2909
2964
  fs6.mkdirSync(CACHE_DIR2, { recursive: true });
2910
2965
  fs6.writeFileSync(FUNCLIB_CACHE_FILE2, JSON.stringify(data), "utf-8");
2911
2966
  } catch (e) {
2912
- log(`writeCacheToDisk error: ${e.message}`);
2967
+ error(`writeCacheToDisk error: ${e.message}`);
2913
2968
  }
2914
2969
  }
2915
2970
  async function loadAtlibFunctionLib() {
@@ -2932,16 +2987,64 @@ async function loadAtlibFunctionLib() {
2932
2987
  writeCacheToDisk2(data);
2933
2988
  return data;
2934
2989
  }
2935
- } catch (e) {
2936
- log(`loadAtlibFunctionLib error: ${e.message}`);
2990
+ } catch (e) {
2991
+ error(`loadAtlibFunctionLib error: ${e.message}`);
2992
+ }
2993
+ return cachedFunctionLib;
2994
+ }
2995
+
2996
+ // src/mcp-tools.js
2997
+ var toolInputSchema = {
2998
+ connect_cad: { platform: "string?" },
2999
+ eval_lisp: { code: "string" },
3000
+ eval_lisp_with_result: { code: "string", encoding: "string?" },
3001
+ get_cad_info: {},
3002
+ list_packages: {},
3003
+ search_packages: { query: "string" },
3004
+ install_package: { packageName: "string" },
3005
+ get_platform_info: {},
3006
+ at_command: { command: "string" },
3007
+ new_document: {},
3008
+ bring_to_front: {},
3009
+ install_atlisp: {},
3010
+ init_atlisp: {},
3011
+ get_function_usage: { name: "string", package: "string?" },
3012
+ list_symbols: { package: "string?" },
3013
+ get_system_status: {},
3014
+ import_funlib: { format: "string?" },
3015
+ list_prompts: {},
3016
+ get_prompt: { name: "string", args: "object?" }
3017
+ };
3018
+ function validateToolArgs(name, args) {
3019
+ const schema = toolInputSchema[name];
3020
+ if (!schema) {
3021
+ throw new Error(`Unknown tool: ${name}`);
3022
+ }
3023
+ for (const [key, type] of Object.entries(schema)) {
3024
+ const isOptional = type.endsWith("?");
3025
+ const baseType = type.replace("?", "");
3026
+ if (!args[key]) {
3027
+ if (isOptional) continue;
3028
+ throw new Error(`Missing required argument: ${key}`);
3029
+ }
3030
+ const actualType = typeof args[key];
3031
+ if (baseType === "string" && actualType !== "string") {
3032
+ throw new Error(`Invalid type for argument ${key}: expected string, got ${actualType}`);
3033
+ }
3034
+ if (baseType === "object" && (actualType !== "object" || actualType === null)) {
3035
+ throw new Error(`Invalid type for argument ${key}: expected object, got ${actualType}`);
3036
+ }
3037
+ if (baseType === "number" && actualType !== "number") {
3038
+ throw new Error(`Invalid type for argument ${key}: expected number, got ${actualType}`);
3039
+ }
2937
3040
  }
2938
- return cachedFunctionLib;
3041
+ return true;
2939
3042
  }
2940
3043
  var TOOL_HANDLERS = {
2941
3044
  connect_cad: (a) => connectCad(a?.platform),
2942
3045
  eval_lisp: (a) => evalLisp(a.code),
2943
3046
  eval_lisp_with_result: (a) => evalLisp(a.code, true, a.encoding),
2944
- get_cad_info: () => getCadInfo(),
3047
+ get_cad_info: () => getCadInfo2(),
2945
3048
  list_packages: () => listPackages(),
2946
3049
  search_packages: (a) => searchPackages(a.query),
2947
3050
  install_package: (a) => installPackage(a.packageName),
@@ -2974,36 +3077,6 @@ ${CAD_PLATFORMS.join("\n")}
2974
3077
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
2975
3078
  }
2976
3079
  };
2977
- function getOrCreateSessionServer(sessionId) {
2978
- let entry = sessionServers.get(sessionId);
2979
- if (!entry) {
2980
- const server = createMcpServer();
2981
- const transport2 = new SessionTransport(sessionId);
2982
- server.connect(transport2);
2983
- entry = { server, transport: transport2 };
2984
- sessionServers.set(sessionId, entry);
2985
- sessionTransports.set(sessionId, transport2);
2986
- }
2987
- return entry;
2988
- }
2989
- function removeSessionServer(sessionId) {
2990
- const entry = sessionServers.get(sessionId);
2991
- if (entry) {
2992
- entry.transport.close().catch(() => {
2993
- });
2994
- entry.server.close().catch(() => {
2995
- });
2996
- sessionServers.delete(sessionId);
2997
- sessionTransports.delete(sessionId);
2998
- }
2999
- }
3000
- function broadcastToAllSessions(uri) {
3001
- for (const [sid, entry] of sessionServers) {
3002
- entry.server.sendResourceUpdated({ uri }).catch((err) => {
3003
- log(`Failed to send resource update to session ${sid}: ${err.message}`);
3004
- });
3005
- }
3006
- }
3007
3080
  var tools = [
3008
3081
  {
3009
3082
  name: "connect_cad",
@@ -3116,7 +3189,7 @@ var tools = [
3116
3189
  inputSchema: {
3117
3190
  type: "object",
3118
3191
  properties: {
3119
- package: { type: "string", description: "\u5305\u540D\uFF0C\u5982 string\uFF08\u53EF\u9009\uFF09" }
3192
+ package: { type: "string", description: "\u5305\u540D\uFF08\u53EF\u9009\uFF09" }
3120
3193
  }
3121
3194
  }
3122
3195
  },
@@ -3153,18 +3226,6 @@ var tools = [
3153
3226
  }
3154
3227
  }
3155
3228
  ];
3156
- async function handleToolCall(name, args) {
3157
- const handler = TOOL_HANDLERS[name];
3158
- if (!handler) return { content: [{ type: "text", text: `\u672A\u77E5\u5DE5\u5177: ${name}` }], isError: true };
3159
- try {
3160
- const result = await handler(args || {});
3161
- notifyResourceChanges(name);
3162
- return result;
3163
- } catch (e) {
3164
- log(`Tool call error [${name}]: ${e.message}`);
3165
- return { content: [{ type: "text", text: `\u5DE5\u5177\u6267\u884C\u9519\u8BEF: ${e.message}` }], isError: true };
3166
- }
3167
- }
3168
3229
  function notifyResourceChanges(toolName) {
3169
3230
  const resourceMap = {
3170
3231
  connect_cad: ["atlisp://cad/info", "atlisp://dwg/layers", "atlisp://dwg/name", "atlisp://dwg/path", "atlisp://cad/dwgs", "atlisp://packages"],
@@ -3183,227 +3244,324 @@ function notifyResourceChanges(toolName) {
3183
3244
  }
3184
3245
  }
3185
3246
  }
3186
- async function initCadConnection() {
3187
- log("Attempting to connect to CAD on startup...");
3247
+ async function handleToolCall(name, args) {
3248
+ const handler = TOOL_HANDLERS[name];
3249
+ if (!handler) return { content: [{ type: "text", text: `\u672A\u77E5\u5DE5\u5177: ${name}` }], isError: true };
3188
3250
  try {
3189
- const connected = await cad.connect();
3190
- if (connected) {
3191
- log("Auto-connected to CAD: " + cad.getPlatform() + " " + cad.getVersion());
3192
- console.error("\u5DF2\u81EA\u52A8\u8FDE\u63A5\u5230 CAD: " + cad.getPlatform() + " " + cad.getVersion());
3193
- } else {
3194
- log("No CAD found on startup");
3195
- }
3251
+ validateToolArgs(name, args || {});
3252
+ const result = await handler(args || {});
3253
+ notifyResourceChanges(name);
3254
+ return result;
3196
3255
  } catch (e) {
3197
- log("Auto-connect failed: " + e.message);
3256
+ error(`Tool call error [${name}]: ${e.message}`, { tool: name, args });
3257
+ return { content: [{ type: "text", text: `\u5DE5\u5177\u6267\u884C\u9519\u8BEF: ${e.message}` }], isError: true };
3198
3258
  }
3199
3259
  }
3260
+
3261
+ // src/mcp-server-state.js
3262
+ var mcpContext = new AsyncLocalStorage();
3200
3263
  var sessionServers = /* @__PURE__ */ new Map();
3201
3264
  var sessionTransports = /* @__PURE__ */ new Map();
3202
- function createMcpServer() {
3203
- const server = new Server(
3204
- { name: SERVER_NAME, version: SERVER_VERSION },
3205
- { capabilities: SERVER_CAPABILITIES }
3206
- );
3207
- server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
3208
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
3209
- const { name, arguments: args } = request.params;
3210
- return await handleToolCall(name, args || {});
3211
- });
3212
- server.setRequestHandler(ListResourcesRequestSchema, async (request) => {
3213
- const ctx = mcpContext.getStore();
3214
- const sessionId = ctx?.sessionId;
3215
- const subscribedUris = sessionId ? list(sessionId) : list();
3216
- return {
3217
- resources: await listResources(subscribedUris)
3218
- };
3219
- });
3220
- server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
3221
- const { uri } = request.params;
3265
+ var mcpHandlers = { tools, handleToolCall };
3266
+ async function getOrCreateSessionServer(sessionId) {
3267
+ let entry = sessionServers.get(sessionId);
3268
+ if (!entry) {
3269
+ const server = createMcpServer(mcpHandlers);
3270
+ const transport = new SessionTransport(sessionId);
3271
+ server.connect(transport);
3272
+ entry = { server, transport };
3273
+ sessionServers.set(sessionId, entry);
3274
+ sessionTransports.set(sessionId, transport);
3275
+ }
3276
+ return entry;
3277
+ }
3278
+ function createStdioServer(handlers) {
3279
+ mcpHandlers = handlers;
3280
+ return createMcpServer(handlers);
3281
+ }
3282
+ function removeSessionServer(sessionId) {
3283
+ const entry = sessionServers.get(sessionId);
3284
+ if (entry) {
3285
+ entry.transport.close().catch(() => {
3286
+ });
3287
+ entry.server.close().catch(() => {
3288
+ });
3289
+ sessionServers.delete(sessionId);
3290
+ sessionTransports.delete(sessionId);
3291
+ }
3292
+ }
3293
+ function broadcastToAllSessions(uri) {
3294
+ for (const [sid, entry] of sessionServers) {
3295
+ entry.server.sendResourceUpdated({ uri }).catch((err) => {
3296
+ error(`Failed to send resource update to session ${sid}: ${err.message}`, { sessionId: sid, uri });
3297
+ });
3298
+ }
3299
+ }
3300
+
3301
+ // src/routes.js
3302
+ var sseSessionManager = new SseSessionManager();
3303
+ var { apiKey, enableCors, corsOrigin, rateLimitWindow, rateLimitMax } = config_default;
3304
+ var mcpLimiter = rateLimit({
3305
+ windowMs: rateLimitWindow,
3306
+ max: rateLimitMax,
3307
+ standardHeaders: true,
3308
+ legacyHeaders: false,
3309
+ message: { error: "\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5" }
3310
+ });
3311
+ var JSON_CONTENT_TYPES = ["application/json", "application/json-rpc", "application/vnd.api+json"];
3312
+ function createJsonBodyParser() {
3313
+ return async (req, res, next) => {
3314
+ const ct = (req.headers["content-type"] || "").toLowerCase();
3315
+ if (!JSON_CONTENT_TYPES.some((t) => ct.startsWith(t))) return next();
3222
3316
  try {
3223
- const data = await readResource(uri);
3224
- return {
3225
- contents: [{
3226
- uri,
3227
- mimeType: "application/json",
3228
- text: JSON.stringify(data)
3229
- }]
3230
- };
3317
+ const chunks = [];
3318
+ for await (const chunk of req) {
3319
+ chunks.push(chunk);
3320
+ }
3321
+ const totalLength = chunks.reduce((acc, c) => acc + c.length, 0);
3322
+ if (totalLength > 10 * 1024 * 1024) {
3323
+ throw new Error("Request body too large");
3324
+ }
3325
+ const buf = Buffer.concat(chunks);
3326
+ const m = ct.match(/charset\s*=\s*([^\s;]+)/i);
3327
+ let charset = m ? m[1].toLowerCase() : "utf-8";
3328
+ const charsetMap = { "gb2312": "gbk", "gb18030": "gbk", "gbk": "gbk", "936": "gbk" };
3329
+ charset = charsetMap[charset] || charset;
3330
+ const str = iconv.decode(buf, charset);
3331
+ req.body = JSON.parse(str);
3332
+ next();
3231
3333
  } catch (e) {
3232
- return {
3233
- contents: [],
3234
- isError: true,
3235
- error: { code: -32603, message: e.message }
3236
- };
3334
+ res.status(400).json({ error: "Invalid JSON body", message: e.message });
3237
3335
  }
3336
+ };
3337
+ }
3338
+ function createLoggingMiddleware() {
3339
+ return (req, res, next) => {
3340
+ log(`${req.method} ${req.path}`, { method: req.method, path: req.path, query: req.query });
3341
+ next();
3342
+ };
3343
+ }
3344
+ function createAuthMiddleware() {
3345
+ const PUBLIC_PATHS = ["/health"];
3346
+ return (req, res, next) => {
3347
+ if (apiKey) {
3348
+ if (PUBLIC_PATHS.includes(req.path)) return next();
3349
+ const auth = req.get("Authorization");
3350
+ if (auth === `Bearer ${apiKey}`) return next();
3351
+ return res.status(401).json({ error: "Unauthorized" });
3352
+ }
3353
+ next();
3354
+ };
3355
+ }
3356
+ function createCorsMiddleware() {
3357
+ return (req, res, next) => {
3358
+ if (!enableCors) return next();
3359
+ res.setHeader("Access-Control-Allow-Origin", corsOrigin);
3360
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, CONNECT");
3361
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Accept, mcp-session-id, last-event-id");
3362
+ res.setHeader("Access-Control-Expose-Headers", "mcp-session-id, content-type, x-accel-buffering");
3363
+ res.setHeader("Access-Control-Max-Age", "86400");
3364
+ res.setHeader("Vary", "Accept");
3365
+ if (req.method === "OPTIONS") return res.status(204).end();
3366
+ next();
3367
+ };
3368
+ }
3369
+ function createErrorHandler() {
3370
+ return (err, req, res, next) => {
3371
+ error(`Unhandled error: ${err.message}`, { stack: err.stack, path: req.path });
3372
+ res.status(500).json({ error: "Internal server error", message: err.message });
3373
+ };
3374
+ }
3375
+ function setupRoutes(app) {
3376
+ app.use(createJsonBodyParser());
3377
+ app.use(createLoggingMiddleware());
3378
+ app.use(createAuthMiddleware());
3379
+ app.use(createCorsMiddleware());
3380
+ app.get("/health", (req, res) => {
3381
+ res.json({ status: "ok", cad: cad.connected ? "connected" : "disconnected" });
3238
3382
  });
3239
- server.setRequestHandler(SubscribeRequestSchema, async (request) => {
3240
- const { uri } = request.params;
3241
- const ctx = mcpContext.getStore();
3242
- subscribe(uri, ctx?.sessionId);
3243
- return { success: true };
3383
+ app.get("/api/docs", (req, res) => {
3384
+ res.json({
3385
+ openapi: "3.0.0",
3386
+ info: { title: "@lisp MCP Server", version: "1.5.0" },
3387
+ paths: {
3388
+ "/mcp": { post: { summary: "MCP JSON-RPC endpoint" } },
3389
+ "/sse": { get: { summary: "Server-Sent Events endpoint" } },
3390
+ "/message": { post: { summary: "SSE message endpoint" } },
3391
+ "/health": { get: { summary: "Health check" } }
3392
+ }
3393
+ });
3244
3394
  });
3245
- server.setRequestHandler(UnsubscribeRequestSchema, async (request) => {
3246
- const { uri } = request.params;
3247
- const ctx = mcpContext.getStore();
3248
- unsubscribe(uri, ctx?.sessionId);
3249
- return { success: true };
3395
+ app.all("/mcp", mcpLimiter, handleMcpPost);
3396
+ app.all("/mcp/:path", mcpLimiter, handleMcpPost);
3397
+ app.get("/mcp/stream", mcpLimiter, handleMcpPost);
3398
+ app.get("/sse", handleSse);
3399
+ app.post("/message", mcpLimiter, handleMessage);
3400
+ app.use(createErrorHandler());
3401
+ return app;
3402
+ }
3403
+ async function handleMcpPost(req, res) {
3404
+ const sessionId = req.headers["mcp-session-id"] || req.query.sessionId || randomUUID4();
3405
+ sessionManager.createSession(sessionId);
3406
+ const body = req.body;
3407
+ if (!body || !body.method) {
3408
+ return res.status(400).json({ error: "Invalid request" });
3409
+ }
3410
+ const { server, transport } = await getOrCreateSessionServer(sessionId);
3411
+ transport.resetCleanupTimer();
3412
+ await mcpContext.run({ sessionId }, async () => {
3413
+ try {
3414
+ const response = await transport.handleRequest(body);
3415
+ if (response) {
3416
+ const headers = { "Content-Type": "application/json" };
3417
+ if (transport.sessionId) {
3418
+ headers["mcp-session-id"] = transport.sessionId;
3419
+ }
3420
+ res.writeHead(200, headers);
3421
+ res.end(JSON.stringify(response));
3422
+ } else {
3423
+ if (!res.headersSent) {
3424
+ res.status(202).end();
3425
+ }
3426
+ }
3427
+ } catch (err) {
3428
+ error(`Transport error [${sessionId}]: ${err.message}`);
3429
+ if (!res.headersSent) {
3430
+ res.status(500).json({ error: "Internal server error", message: err.message });
3431
+ }
3432
+ }
3250
3433
  });
3251
- server.setRequestHandler(ListPromptsRequestSchema, async () => ({
3252
- prompts: await listPrompts()
3253
- }));
3254
- server.setRequestHandler(GetPromptRequestSchema, async (request) => {
3255
- const { name, arguments: args } = request.params;
3256
- return await getPrompt(name, args || {});
3434
+ }
3435
+ async function handleSse(req, res) {
3436
+ const sessionId = randomUUID4();
3437
+ sessionManager.createSession(sessionId);
3438
+ getOrCreateSessionServer(sessionId);
3439
+ const sseSession = new SseSession(res, { clientId: sessionId });
3440
+ sseSessionManager.add(sessionId, sseSession);
3441
+ sseSession.sendEndpoint(`/message?sessionId=${sessionId}`);
3442
+ req.on("close", () => {
3443
+ sseSessionManager.remove(sessionId);
3444
+ removeSessionServer(sessionId);
3257
3445
  });
3258
- return server;
3446
+ }
3447
+ async function handleMessage(req, res) {
3448
+ const sessionId = req.query.sessionId;
3449
+ if (!sessionId) {
3450
+ return res.status(400).json({ error: "Missing sessionId" });
3451
+ }
3452
+ const sseSession = sseSessionManager.get(sessionId);
3453
+ if (!sseSession) {
3454
+ return res.status(404).json({ error: "Session not found" });
3455
+ }
3456
+ sessionManager.createSession(sessionId);
3457
+ const { server, transport } = await getOrCreateSessionServer(sessionId);
3458
+ transport.resetCleanupTimer();
3459
+ const body = req.body;
3460
+ if (body && body.method) {
3461
+ try {
3462
+ const response = await transport.handleRequest(body);
3463
+ if (response) {
3464
+ sseSession.sendResponse(response);
3465
+ }
3466
+ if (!res.headersSent) {
3467
+ res.status(202).end();
3468
+ }
3469
+ } catch (err) {
3470
+ error(`SSE message error [${sessionId}]: ${err.message}`);
3471
+ if (!res.headersSent) {
3472
+ res.status(500).json({ error: "Internal server error" });
3473
+ }
3474
+ }
3475
+ } else {
3476
+ res.status(400).json({ error: "Invalid request" });
3477
+ }
3478
+ }
3479
+
3480
+ // src/atlisp-mcp.js
3481
+ var __dirname4 = path8.dirname(fileURLToPath4(import.meta.url));
3482
+ var require2 = createRequire(import.meta.url);
3483
+ var { version } = require2(path8.join(__dirname4, "..", "package.json"));
3484
+ var sseSessionManager2 = new SseSessionManager();
3485
+ var isMcpScript = process.argv[1]?.includes("atlisp-mcp");
3486
+ if (isMcpScript) {
3487
+ const args = process.argv.slice(2);
3488
+ for (let i = 0; i < args.length; i++) {
3489
+ if (args[i] === "--version" || args[i] === "-v") {
3490
+ console.log(`atlisp-mcp v${version}`);
3491
+ process.exit(0);
3492
+ } else if (args[i] === "--help" || args[i] === "-h") {
3493
+ console.log(`
3494
+ @lisp MCP Server v${version}
3495
+
3496
+ Usage: atlisp-mcp [options]
3497
+
3498
+ Options:
3499
+ -v, --version Show version number
3500
+ --transport <type> Transport type: http or stdio (default: http)
3501
+ --port <port> HTTP port (default: 8110)
3502
+ --host <host> HTTP host (default: 0.0.0.0)
3503
+ --stdio Shorthand for --transport stdio
3504
+ -h, --help Show this help message
3505
+
3506
+ Examples:
3507
+ atlisp-mcp # Start HTTP server on port 8110
3508
+ atlisp-mcp --port 3000 # Start HTTP server on port 3000
3509
+ atlisp-mcp --stdio # Start in stdio mode for MCP clients
3510
+ atlisp-mcp --transport stdio # Same as --stdio
3511
+ `);
3512
+ process.exit(0);
3513
+ } else if (args[i] === "--transport" && args[i + 1]) {
3514
+ process.env.TRANSPORT = args[i + 1];
3515
+ i++;
3516
+ } else if (args[i] === "--stdio") {
3517
+ process.env.TRANSPORT = "stdio";
3518
+ } else if (args[i] === "--port" && args[i + 1]) {
3519
+ process.env.PORT = args[i + 1];
3520
+ i++;
3521
+ } else if (args[i] === "--host" && args[i + 1]) {
3522
+ process.env.HOST = args[i + 1];
3523
+ i++;
3524
+ }
3525
+ }
3526
+ }
3527
+ if (config_default.apiKey) log("API Key authentication enabled");
3528
+ async function initCadConnection() {
3529
+ log("Attempting to connect to CAD on startup...");
3530
+ try {
3531
+ const connected = await cad.connect();
3532
+ if (connected) {
3533
+ log(`Auto-connected to CAD: ${cad.getPlatform()} ${cad.getVersion()}`);
3534
+ console.error(`\u5DF2\u81EA\u52A8\u8FDE\u63A5\u5230 CAD: ${cad.getPlatform()} ${cad.getVersion()}`);
3535
+ } else {
3536
+ log("No CAD found on startup");
3537
+ }
3538
+ } catch (e) {
3539
+ error(`Auto-connect failed: ${e.message}`);
3540
+ }
3259
3541
  }
3260
3542
  async function startServer() {
3261
3543
  if (config_default.transport === "stdio") {
3262
3544
  await initCadConnection();
3263
- const mcpServer = createMcpServer();
3264
- const transport2 = new StdioServerTransport();
3545
+ const mcpServer = createStdioServer({ tools, handleToolCall });
3546
+ const transport = new StdioServerTransport();
3265
3547
  const sessionId = "stdio-session";
3266
3548
  sessionManager.createSession(sessionId);
3267
3549
  await mcpContext.run({ sessionId }, async () => {
3268
- await mcpServer.connect(transport2);
3550
+ await mcpServer.connect(transport);
3269
3551
  });
3270
3552
  console.error(`${SERVER_NAME} \u8FD0\u884C\u5728 stdio \u6A21\u5F0F`);
3271
3553
  setNotify((uri, data) => {
3272
3554
  mcpServer.sendResourceUpdated({ uri }).catch((err) => {
3273
- log(`Failed to send resource update: ${err.message}`);
3555
+ error(`Failed to send resource update: ${err.message}`);
3274
3556
  });
3275
3557
  });
3276
3558
  } else {
3277
- const app = express();
3278
- const JSON_CONTENT_TYPES = ["application/json", "application/json-rpc", "application/vnd.api+json"];
3279
- app.use(async (req, res, next) => {
3280
- const ct = (req.headers["content-type"] || "").toLowerCase();
3281
- if (!JSON_CONTENT_TYPES.some((t) => ct.startsWith(t))) return next();
3282
- try {
3283
- const buf = await rawBody(req, { limit: "10mb" });
3284
- const m = ct.match(/charset\s*=\s*([^\s;]+)/i);
3285
- let charset = m ? m[1].toLowerCase() : "utf-8";
3286
- const charsetMap = { "gb2312": "gbk", "gb18030": "gbk", "gbk": "gbk", "936": "gbk" };
3287
- charset = charsetMap[charset] || charset;
3288
- const str = encodingExists(charset) ? charsetDecode(buf, charset) : buf.toString("utf-8");
3289
- req.body = JSON.parse(str);
3290
- next();
3291
- } catch (e) {
3292
- res.status(400).json({ error: "Invalid JSON body", message: e.message });
3293
- }
3294
- });
3295
- app.use((req, res, next) => {
3296
- log(`${req.method} ${req.path}`);
3297
- next();
3298
- });
3299
- const PUBLIC_PATHS = ["/health"];
3300
- if (apiKey) {
3301
- app.use((req, res, next) => {
3302
- if (PUBLIC_PATHS.includes(req.path)) return next();
3303
- const auth = req.get("Authorization");
3304
- if (auth === `Bearer ${apiKey}`) return next();
3305
- res.status(401).json({ error: "Unauthorized" });
3306
- });
3307
- }
3308
- app.get("/health", (req, res) => {
3309
- res.json({ status: "ok", cad: cad.connected ? "connected" : "disconnected" });
3310
- });
3311
- if (config_default.enableCors) {
3312
- app.use((req, res, next) => {
3313
- res.setHeader("Access-Control-Allow-Origin", config_default.corsOrigin);
3314
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, CONNECT");
3315
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Accept, mcp-session-id, last-event-id");
3316
- res.setHeader("Access-Control-Expose-Headers", "mcp-session-id, content-type, x-accel-buffering");
3317
- res.setHeader("Access-Control-Max-Age", "86400");
3318
- res.setHeader("Vary", "Accept");
3319
- if (req.method === "OPTIONS") return res.status(204).end();
3320
- next();
3321
- });
3322
- }
3559
+ const app = express2();
3560
+ setupRoutes(app);
3323
3561
  setNotify((uri, data) => {
3324
3562
  broadcastToAllSessions(uri);
3325
- for (const session of sseSessionManager.listActive()) {
3326
- sseSessionManager.sendToClient(session.clientId, "resource_updated", { uri });
3327
- }
3328
- });
3329
- async function handleMcpPost(req, res) {
3330
- const sessionId = req.headers["mcp-session-id"] || req.query.sessionId || randomUUID4();
3331
- sessionManager.createSession(sessionId);
3332
- const body = req.body;
3333
- if (!body || !body.method) {
3334
- return res.status(400).json({ error: "Invalid request" });
3335
- }
3336
- const { server, transport: transport2 } = getOrCreateSessionServer(sessionId);
3337
- transport2.resetCleanupTimer();
3338
- await mcpContext.run({ sessionId }, async () => {
3339
- try {
3340
- const response = await transport2.handleRequest(body);
3341
- if (response) {
3342
- const headers = { "Content-Type": "application/json" };
3343
- if (transport2.sessionId) {
3344
- headers["mcp-session-id"] = transport2.sessionId;
3345
- }
3346
- res.writeHead(200, headers);
3347
- res.end(JSON.stringify(response));
3348
- } else {
3349
- if (!res.headersSent) {
3350
- res.status(202).end();
3351
- }
3352
- }
3353
- } catch (error) {
3354
- log(`Transport error [${sessionId}]: ${error.message}`);
3355
- if (!res.headersSent) {
3356
- res.status(500).json({ error: "Internal server error", message: error.message });
3357
- }
3358
- }
3359
- });
3360
- }
3361
- app.all("/mcp", mcpLimiter, handleMcpPost);
3362
- app.all("/mcp/:path", mcpLimiter, handleMcpPost);
3363
- app.get("/mcp/stream", mcpLimiter, handleMcpPost);
3364
- const sseEndpoint = "/sse";
3365
- app.get(sseEndpoint, async (req, res) => {
3366
- const sessionId = randomUUID4();
3367
- sessionManager.createSession(sessionId);
3368
- getOrCreateSessionServer(sessionId);
3369
- const sseSession = new SseSession(res, { clientId: sessionId });
3370
- sseSessionManager.add(sessionId, sseSession);
3371
- sseSession.sendEndpoint(`/message?sessionId=${sessionId}`);
3372
- req.on("close", () => {
3373
- sseSessionManager.remove(sessionId);
3374
- removeSessionServer(sessionId);
3375
- });
3376
- });
3377
- app.post("/message", mcpLimiter, async (req, res) => {
3378
- const sessionId = req.query.sessionId;
3379
- if (!sessionId) {
3380
- return res.status(400).json({ error: "Missing sessionId" });
3381
- }
3382
- const sseSession = sseSessionManager.get(sessionId);
3383
- if (!sseSession) {
3384
- return res.status(404).json({ error: "Session not found" });
3385
- }
3386
- sessionManager.createSession(sessionId);
3387
- const { server, transport: transport2 } = getOrCreateSessionServer(sessionId);
3388
- transport2.resetCleanupTimer();
3389
- const body = req.body;
3390
- if (body && body.method) {
3391
- try {
3392
- const response = await transport2.handleRequest(body);
3393
- if (response) {
3394
- sseSession.sendResponse(response);
3395
- }
3396
- if (!res.headersSent) {
3397
- res.status(202).end();
3398
- }
3399
- } catch (error) {
3400
- log(`SSE message error [${sessionId}]: ${error.message}`);
3401
- if (!res.headersSent) {
3402
- res.status(500).json({ error: "Internal server error" });
3403
- }
3404
- }
3405
- } else {
3406
- res.status(400).json({ error: "Invalid request" });
3563
+ for (const session of sseSessionManager2.listActive()) {
3564
+ sseSessionManager2.sendToClient(session.clientId, "resource_updated", { uri });
3407
3565
  }
3408
3566
  });
3409
3567
  const httpServer = app.listen(config_default.port, config_default.host, async () => {
@@ -3413,7 +3571,7 @@ async function startServer() {
3413
3571
  async function shutdown(signal) {
3414
3572
  console.error(`
3415
3573
  \u6536\u5230 ${signal}\uFF0C\u6B63\u5728\u4F18\u96C5\u5173\u95ED...`);
3416
- sseSessionManager.closeAll();
3574
+ sseSessionManager2.closeAll();
3417
3575
  sessionManager.clear();
3418
3576
  for (const [sid] of sessionServers) {
3419
3577
  removeSessionServer(sid);
@@ -3433,8 +3591,6 @@ if (!process.env.VITEST) {
3433
3591
  export {
3434
3592
  CAD_PLATFORMS,
3435
3593
  FILE_EXTENSIONS,
3436
- MOCK_PACKAGES,
3437
- createMcpServer,
3438
3594
  handleToolCall,
3439
3595
  loadAtlibFunctionLib,
3440
3596
  startServer,