@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.
- package/dist/atlisp-mcp.js +1967 -1811
- package/package.json +2 -2
package/dist/atlisp-mcp.js
CHANGED
|
@@ -1,114 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/atlisp-mcp.js
|
|
4
|
-
import
|
|
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
|
|
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
|
|
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(
|
|
158
|
-
host:
|
|
159
|
-
transport: process.env.TRANSPORT
|
|
160
|
-
apiKey:
|
|
161
|
-
enableSse:
|
|
162
|
-
messageTimeout: parseInt(
|
|
163
|
-
heartbeatInterval: parseInt(
|
|
164
|
-
busyRetries: parseInt(
|
|
165
|
-
busyDelay: parseInt(
|
|
166
|
-
enableCors:
|
|
167
|
-
corsOrigin:
|
|
168
|
-
rateLimitWindow: parseInt(
|
|
169
|
-
rateLimitMax: parseInt(
|
|
170
|
-
resourceCacheTtl: parseInt(
|
|
171
|
-
|
|
172
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
480
|
-
const entry =
|
|
481
|
-
|
|
482
|
-
|
|
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/
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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
|
-
|
|
454
|
+
resourceCache.delete(key);
|
|
455
|
+
return null;
|
|
505
456
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
if (
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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
|
-
}
|
|
527
|
-
|
|
467
|
+
} else {
|
|
468
|
+
resourceCache.clear();
|
|
528
469
|
}
|
|
529
470
|
}
|
|
530
|
-
|
|
531
|
-
if (!
|
|
532
|
-
|
|
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 (
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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
|
-
|
|
546
|
-
if (!
|
|
547
|
-
|
|
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
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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
|
|
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
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
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
|
-
|
|
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
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
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
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
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 (
|
|
717
|
-
|
|
643
|
+
if (entType === "INSERT" && rest[0] != null) {
|
|
644
|
+
entity.blockName = String(rest[0]);
|
|
718
645
|
}
|
|
719
|
-
return
|
|
720
|
-
${results.map((p) => `${p.name} v${p.version || ""} - ${p.description || ""}`).join("\n")}` }] };
|
|
646
|
+
return entity;
|
|
721
647
|
}
|
|
722
|
-
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
if (
|
|
727
|
-
|
|
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
|
-
|
|
730
|
-
|
|
731
|
-
`
|
|
732
|
-
|
|
733
|
-
|
|
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
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
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
|
-
|
|
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
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
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
|
|
765
|
-
const
|
|
766
|
-
if (
|
|
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
|
-
|
|
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(`
|
|
808
|
+
log(`Error checking CAD busy status: ${e.message}`);
|
|
776
809
|
}
|
|
777
|
-
|
|
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
|
|
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
|
-
|
|
782
|
-
const
|
|
783
|
-
if (
|
|
784
|
-
|
|
785
|
-
|
|
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
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
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 (
|
|
801
|
-
|
|
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
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
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
|
-
|
|
864
|
+
setCache(cacheKey, []);
|
|
865
|
+
return [];
|
|
812
866
|
}
|
|
813
867
|
}
|
|
814
|
-
async function
|
|
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
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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
|
-
|
|
849
|
-
|
|
850
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
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
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
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
|
-
|
|
885
|
-
|
|
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
|
|
889
|
-
if (!
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
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
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
974
|
+
let parsed = null;
|
|
975
|
+
try {
|
|
976
|
+
parsed = JSON.parse(result);
|
|
977
|
+
} catch {
|
|
915
978
|
}
|
|
916
|
-
if (
|
|
917
|
-
|
|
918
|
-
|
|
979
|
+
if (!parsed) {
|
|
980
|
+
const listResult = parseLispList(result);
|
|
981
|
+
if (listResult && listResult.length >= 2) parsed = listResult;
|
|
919
982
|
}
|
|
920
|
-
if (
|
|
921
|
-
|
|
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
|
-
|
|
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
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
if (
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
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
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1054
|
+
setCache(cacheKey, packages);
|
|
1055
|
+
return packages;
|
|
1056
|
+
} catch (e) {
|
|
1057
|
+
setCache(cacheKey, []);
|
|
1058
|
+
return [];
|
|
1024
1059
|
}
|
|
1025
|
-
return parseValue();
|
|
1026
1060
|
}
|
|
1027
|
-
function
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
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
|
|
1040
|
-
|
|
1041
|
-
const
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
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
|
-
|
|
1061
|
-
|
|
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
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
if (
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
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
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
(
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
(
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
(
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
(
|
|
1094
|
-
|
|
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
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
(
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
(
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
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
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
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
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
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
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1217
|
-
|
|
1218
|
-
setCache("cad:info", result2);
|
|
1219
|
-
return result2;
|
|
1301
|
+
get lastEventId() {
|
|
1302
|
+
return this.#lastEventId;
|
|
1220
1303
|
}
|
|
1221
|
-
|
|
1222
|
-
|
|
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
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
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
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
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
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
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
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
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
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
}
|
|
1345
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1368
|
-
if (!cad.connected) await cad.connect();
|
|
1369
|
-
if (!cad.connected) return { name: null };
|
|
1505
|
+
function loadPersistedSessionData(clientId) {
|
|
1370
1506
|
try {
|
|
1371
|
-
const
|
|
1372
|
-
|
|
1373
|
-
|
|
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
|
|
1517
|
+
return null;
|
|
1376
1518
|
}
|
|
1377
1519
|
}
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
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
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
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
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
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
|
-
|
|
1401
|
-
|
|
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
|
|
1404
|
-
} catch (e) {
|
|
1405
|
-
return { path: null, name: null };
|
|
1573
|
+
return session.sendEvent(event, data, id);
|
|
1406
1574
|
}
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
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
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
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
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
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
|
-
|
|
1472
|
-
|
|
1473
|
-
} catch (e) {
|
|
1474
|
-
setCache(cacheKey, []);
|
|
1475
|
-
return [];
|
|
1686
|
+
this._clearCleanupTimer();
|
|
1687
|
+
this.onclose?.();
|
|
1476
1688
|
}
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
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
|
-
|
|
1505
|
-
|
|
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 (!
|
|
1514
|
-
|
|
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
|
-
}
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
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
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
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
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
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/
|
|
2333
|
-
var
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
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
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
}
|
|
2360
|
-
|
|
2361
|
-
const
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
return
|
|
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
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
const
|
|
2370
|
-
|
|
2371
|
-
|
|
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
|
-
|
|
2584
|
+
} catch (e) {
|
|
2585
|
+
log("connect_cad error: " + e.message);
|
|
2374
2586
|
}
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
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
|
-
|
|
2380
|
-
|
|
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
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
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
|
-
|
|
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
|
|
2421
|
-
if (
|
|
2422
|
-
|
|
2613
|
+
async function getCadInfo2() {
|
|
2614
|
+
if (!cad.connected) {
|
|
2615
|
+
await cad.connect();
|
|
2423
2616
|
}
|
|
2424
|
-
return
|
|
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
|
|
2427
|
-
if (
|
|
2428
|
-
|
|
2429
|
-
}
|
|
2430
|
-
|
|
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
|
|
2433
|
-
if (
|
|
2434
|
-
|
|
2628
|
+
async function newDocument() {
|
|
2629
|
+
if (!cad.connected) {
|
|
2630
|
+
await cad.connect();
|
|
2435
2631
|
}
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
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
|
-
|
|
2445
|
-
|
|
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
|
-
|
|
2510
|
-
|
|
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
|
-
|
|
2513
|
-
|
|
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
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
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
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
}
|
|
2533
|
-
|
|
2534
|
-
|
|
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
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
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
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
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
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
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
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
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
|
-
|
|
2584
|
-
this.close();
|
|
2585
|
-
return false;
|
|
2705
|
+
status.lisp.error = e.message;
|
|
2586
2706
|
}
|
|
2587
|
-
}
|
|
2588
|
-
_flush() {
|
|
2589
2707
|
try {
|
|
2590
|
-
|
|
2591
|
-
|
|
2708
|
+
const pkgResult = await cad.sendCommandWithResult("(@::package-list-in-local)");
|
|
2709
|
+
if (pkgResult && pkgResult !== "nil") {
|
|
2710
|
+
status.packages = pkgResult;
|
|
2592
2711
|
}
|
|
2593
|
-
} catch
|
|
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
|
-
|
|
2614
|
-
|
|
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
|
-
|
|
2617
|
-
|
|
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
|
-
|
|
2620
|
-
|
|
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
|
-
|
|
2623
|
-
|
|
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
|
-
|
|
2626
|
-
|
|
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
|
-
|
|
2629
|
-
|
|
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
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
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
|
-
|
|
2636
|
-
|
|
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
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
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
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
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/
|
|
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
|
|
2675
|
-
var
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
function
|
|
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.
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
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
|
-
|
|
2844
|
+
log(`function-handlers writeCache error: ${e.message}`);
|
|
2690
2845
|
}
|
|
2691
2846
|
}
|
|
2692
|
-
function
|
|
2847
|
+
async function fetchFunctions() {
|
|
2848
|
+
const diskCache = readCacheFromDisk();
|
|
2849
|
+
if (diskCache) return diskCache;
|
|
2693
2850
|
try {
|
|
2694
|
-
const
|
|
2695
|
-
if (
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
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
|
-
|
|
2858
|
+
log(`function-handlers fetch error: ${e.message}`);
|
|
2705
2859
|
}
|
|
2860
|
+
return diskCache;
|
|
2706
2861
|
}
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
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
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
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
|
-
|
|
2725
|
-
|
|
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
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
return
|
|
2767
|
-
}
|
|
2768
|
-
|
|
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
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
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
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2818
|
-
|
|
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
|
-
|
|
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/
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
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
|
|
2833
|
-
var
|
|
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 <
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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: () =>
|
|
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\
|
|
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
|
|
3187
|
-
|
|
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
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
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
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
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
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
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
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
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
|
-
|
|
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 =
|
|
3264
|
-
const
|
|
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(
|
|
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
|
-
|
|
3555
|
+
error(`Failed to send resource update: ${err.message}`);
|
|
3274
3556
|
});
|
|
3275
3557
|
});
|
|
3276
3558
|
} else {
|
|
3277
|
-
const app =
|
|
3278
|
-
|
|
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
|
|
3326
|
-
|
|
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
|
-
|
|
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,
|