@atlisp/mcp 1.8.30 → 1.8.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -189,6 +189,30 @@ atlisp-mcp --ws
189
189
  atlisp-mcp --port 9000 --host 127.0.0.1
190
190
  ```
191
191
 
192
+ > **端口防冲突**:每个端口只允许一个实例运行。启动时自动检测端口是否已被其他 `atlisp-mcp` 进程占用(通过 `~/.atlisp/locks/atlisp-mcp-{port}.lock` PID 锁文件),占用则报错退出。不同端口可同时运行多个实例互不干扰。
193
+
194
+ ### 设置开机自启
195
+
196
+ 注册到用户登录后自动启动 HTTP 服务:
197
+
198
+ ```bash
199
+ atlisp-mcp --install-autostart
200
+ ```
201
+
202
+ 移除自启:
203
+
204
+ ```bash
205
+ atlisp-mcp --uninstall-autostart
206
+ ```
207
+
208
+ 查看自启状态:
209
+
210
+ ```bash
211
+ atlisp-mcp --autostart-status
212
+ ```
213
+
214
+ 支持三种平台:Windows(Registry Run 键)、macOS(LaunchAgent)、Linux(XDG Autostart)。
215
+
192
216
  ### 健康检查
193
217
 
194
218
  ```bash
@@ -824,6 +848,9 @@ curl http://localhost:8110/metrics
824
848
  | `--config <path>` | 配置文件路径 |
825
849
  | `--ssl-key <path>` | SSL 私钥文件 |
826
850
  | `--ssl-cert <path>` | SSL 证书文件 |
851
+ | `--install-autostart` | 注册用户登录后自动启动 HTTP 服务 |
852
+ | `--uninstall-autostart` | 移除自动启动注册 |
853
+ | `--autostart-status` | 查询自动启动是否已安装 |
827
854
 
828
855
  ---
829
856
 
@@ -880,7 +907,7 @@ curl http://localhost:8110/metrics
880
907
  | 中文乱码 | 使用 `encoding: gbk` 参数或用 `vl-prin1-to-string` 处理 |
881
908
  | 括号错误 | MCP Server 会自动检查并提示 |
882
909
  | CAD 未自动启动 | 检查 COM 权限,以管理员身份运行 |
883
- | 端口被占用 | 使用 `--port` 指定其他端口 |
910
+ | 端口被占用 | 使用 `--port` 指定其他端口;同端口多实例冲突时,结束原进程或换端口 |
884
911
  | 全局命令找不到 | 检查 npm 全局安装路径是否在 PATH 中 |
885
912
 
886
913
  ---
@@ -7,10 +7,10 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
7
7
  if (typeof require !== "undefined") return require.apply(this, arguments);
8
8
  throw Error('Dynamic require of "' + x + '" is not supported');
9
9
  });
10
- var __glob = (map) => (path21) => {
11
- var fn = map[path21];
10
+ var __glob = (map) => (path23) => {
11
+ var fn = map[path23];
12
12
  if (fn) return fn();
13
- throw new Error("Module not found in bundle: " + path21);
13
+ throw new Error("Module not found in bundle: " + path23);
14
14
  };
15
15
  var __esm = (fn, res) => function __init() {
16
16
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
@@ -1894,8 +1894,8 @@ __export(renderer_exports, {
1894
1894
  renderPrompt: () => renderPrompt,
1895
1895
  renderTemplate: () => renderTemplate
1896
1896
  });
1897
- function resolvePath(obj, path21) {
1898
- const parts = path21.split(/[.\[\]]/g).filter(Boolean);
1897
+ function resolvePath(obj, path23) {
1898
+ const parts = path23.split(/[.\[\]]/g).filter(Boolean);
1899
1899
  let val = obj;
1900
1900
  for (const p of parts) {
1901
1901
  if (val == null) return void 0;
@@ -1905,8 +1905,8 @@ function resolvePath(obj, path21) {
1905
1905
  }
1906
1906
  function renderTemplate(template, args) {
1907
1907
  return template.replace(PARAM_RE, (match, rawExpr, expr) => {
1908
- const path21 = rawExpr || expr;
1909
- const val = resolvePath(args, path21);
1908
+ const path23 = rawExpr || expr;
1909
+ const val = resolvePath(args, path23);
1910
1910
  if (val == null) return "";
1911
1911
  const strVal = String(val);
1912
1912
  if (rawExpr) return strVal;
@@ -2338,8 +2338,8 @@ var init_sse_session = __esm({
2338
2338
  sendMessage(data) {
2339
2339
  return this.sendEvent(SSE_EVENTS.MESSAGE, data);
2340
2340
  }
2341
- sendEndpoint(path21) {
2342
- return this.sendEvent(SSE_EVENTS.ENDPOINT, path21);
2341
+ sendEndpoint(path23) {
2342
+ return this.sendEvent(SSE_EVENTS.ENDPOINT, path23);
2343
2343
  }
2344
2344
  sendError(code, message, id = null) {
2345
2345
  return this.sendEvent(SSE_EVENTS.ERROR, { code, message }, id);
@@ -14376,8 +14376,8 @@ async function exportXdataToFile(handle, filePath) {
14376
14376
  await ensureCadConnected();
14377
14377
  const xdataResult = await getAllEntityXdata(handle);
14378
14378
  const parsed = JSON.parse(xdataResult.content[0].text);
14379
- const fs17 = await import("fs");
14380
- fs17.writeFileSync(filePath, JSON.stringify(parsed.xdata, null, 2));
14379
+ const fs19 = await import("fs");
14380
+ fs19.writeFileSync(filePath, JSON.stringify(parsed.xdata, null, 2));
14381
14381
  return mcpSuccess(JSON.stringify({ handle, filePath }));
14382
14382
  } catch (e) {
14383
14383
  return mcpError(`\u9519\u8BEF: ${e.message}`);
@@ -14386,8 +14386,8 @@ async function exportXdataToFile(handle, filePath) {
14386
14386
  async function importXdataFromFile(handle, filePath, appName) {
14387
14387
  try {
14388
14388
  await ensureCadConnected();
14389
- const fs17 = await import("fs");
14390
- const data = fs17.readFileSync(filePath, "utf8");
14389
+ const fs19 = await import("fs");
14390
+ const data = fs19.readFileSync(filePath, "utf8");
14391
14391
  const xdata = JSON.parse(data);
14392
14392
  if (appName && xdata[appName]) {
14393
14393
  return await setXdata(handle, appName, xdata[appName]);
@@ -14436,13 +14436,13 @@ function lispPoint(pt) {
14436
14436
  }
14437
14437
  async function attachXref(args) {
14438
14438
  try {
14439
- const path21 = esc3(args.path);
14439
+ const path23 = esc3(args.path);
14440
14440
  const point = lispPoint(args.point);
14441
14441
  const scale = args.scale || 1;
14442
14442
  const rotation = args.rotation || 0;
14443
14443
  const overlay = args.overlay ? "_O" : "_A";
14444
14444
  const result = await cad.sendCommandWithResult(
14445
- `(command "_.XATTACH" "${path21}" "${overlay}" "${point}" "${scale}" "${rotation}") (princ)`
14445
+ `(command "_.XATTACH" "${path23}" "${overlay}" "${point}" "${scale}" "${rotation}") (princ)`
14446
14446
  );
14447
14447
  return mcpSuccess(`\u5916\u90E8\u53C2\u7167\u5DF2\u9644\u7740: ${args.path}`);
14448
14448
  } catch (e) {
@@ -15586,8 +15586,8 @@ async function evaluateWatch(watch) {
15586
15586
  );
15587
15587
  const arr = parseLispList2(result) || [];
15588
15588
  const matched = Number(arr[0]) !== 0;
15589
- const path21 = String(arr[1] || "");
15590
- return { matched, data: { matched: Boolean(matched), path: path21 } };
15589
+ const path23 = String(arr[1] || "");
15590
+ return { matched, data: { matched: Boolean(matched), path: path23 } };
15591
15591
  }
15592
15592
  case "entity_selected": {
15593
15593
  result = await cad.sendCommandWithResult(
@@ -16203,9 +16203,9 @@ var init_agent_context = __esm({
16203
16203
  function validationError(msg) {
16204
16204
  return new MCPError(ERROR_CODES.INVALID_ARGS, msg);
16205
16205
  }
16206
- function validateProps(args, props, required, path21 = "") {
16206
+ function validateProps(args, props, required, path23 = "") {
16207
16207
  for (const [key, prop] of Object.entries(props)) {
16208
- const fullKey = path21 ? `${path21}.${key}` : key;
16208
+ const fullKey = path23 ? `${path23}.${key}` : key;
16209
16209
  const isOptional = !required.includes(key);
16210
16210
  const val = args[key];
16211
16211
  if (val === void 0 || val === null) {
@@ -18796,8 +18796,8 @@ init_subscription_manager();
18796
18796
  init_subscription_manager();
18797
18797
  init_sse_session();
18798
18798
  init_config();
18799
- import path20 from "path";
18800
- import fs16 from "fs";
18799
+ import path22 from "path";
18800
+ import fs18 from "fs";
18801
18801
  import http from "http";
18802
18802
  import https from "https";
18803
18803
  import { fileURLToPath as fileURLToPath8 } from "url";
@@ -19489,10 +19489,10 @@ function sendWelcome(ws, clientId) {
19489
19489
  }
19490
19490
  }
19491
19491
  function startPingPong(ws, clientId) {
19492
- let isAlive = true;
19492
+ let isAlive2 = true;
19493
19493
  ws._isAlive = true;
19494
19494
  ws.on("pong", () => {
19495
- isAlive = true;
19495
+ isAlive2 = true;
19496
19496
  ws._isAlive = true;
19497
19497
  });
19498
19498
  const interval = setInterval(() => {
@@ -19500,13 +19500,13 @@ function startPingPong(ws, clientId) {
19500
19500
  clearInterval(interval);
19501
19501
  return;
19502
19502
  }
19503
- if (!isAlive) {
19503
+ if (!isAlive2) {
19504
19504
  clearInterval(interval);
19505
19505
  log(`WebSocket ping timeout, terminating [${clientId}]`);
19506
19506
  ws.terminate();
19507
19507
  return;
19508
19508
  }
19509
- isAlive = false;
19509
+ isAlive2 = false;
19510
19510
  ws._isAlive = false;
19511
19511
  try {
19512
19512
  ws.ping();
@@ -19989,13 +19989,228 @@ function setupStreamableHttpRoutes(app) {
19989
19989
  });
19990
19990
  }
19991
19991
 
19992
+ // src/lockfile.js
19993
+ import fs16 from "fs";
19994
+ import path20 from "path";
19995
+ import os13 from "os";
19996
+ var LOCK_DIR = path20.join(os13.homedir(), ".atlisp", "locks");
19997
+ function ensureDir2() {
19998
+ if (!fs16.existsSync(LOCK_DIR)) {
19999
+ fs16.mkdirSync(LOCK_DIR, { recursive: true });
20000
+ }
20001
+ }
20002
+ function lockPath(port) {
20003
+ return path20.join(LOCK_DIR, `atlisp-mcp-${port}.lock`);
20004
+ }
20005
+ function isAlive(pid) {
20006
+ try {
20007
+ process.kill(pid, 0);
20008
+ return true;
20009
+ } catch (e) {
20010
+ return e.code === "EPERM";
20011
+ }
20012
+ }
20013
+ function acquirePortLock(port, host) {
20014
+ ensureDir2();
20015
+ const lp = lockPath(port);
20016
+ const payload = JSON.stringify({ pid: process.pid, port, host, started: (/* @__PURE__ */ new Date()).toISOString() });
20017
+ for (let attempt = 0; attempt < 3; attempt++) {
20018
+ try {
20019
+ const fd = fs16.openSync(lp, "wx");
20020
+ fs16.writeSync(fd, payload, 0, "utf-8");
20021
+ fs16.closeSync(fd);
20022
+ return { ok: true };
20023
+ } catch (err) {
20024
+ if (err.code !== "EEXIST") throw err;
20025
+ try {
20026
+ const existing = JSON.parse(fs16.readFileSync(lp, "utf-8"));
20027
+ if (existing.pid && isAlive(existing.pid)) {
20028
+ return { ok: false, pid: existing.pid, started: existing.started };
20029
+ }
20030
+ } catch {
20031
+ }
20032
+ try {
20033
+ fs16.unlinkSync(lp);
20034
+ } catch {
20035
+ }
20036
+ }
20037
+ }
20038
+ return { ok: false, pid: null, started: null };
20039
+ }
20040
+ function releasePortLock(port) {
20041
+ try {
20042
+ const lp = lockPath(port);
20043
+ try {
20044
+ const existing = JSON.parse(fs16.readFileSync(lp, "utf-8"));
20045
+ if (existing.pid !== process.pid) return;
20046
+ } catch {
20047
+ }
20048
+ fs16.unlinkSync(lp);
20049
+ } catch {
20050
+ }
20051
+ }
20052
+
20053
+ // src/autostart.js
20054
+ import { execSync } from "child_process";
20055
+ import path21 from "path";
20056
+ import fs17 from "fs";
20057
+ import os14 from "os";
20058
+ var AUTOSTART_NAME = "atlisp-mcp";
20059
+ function getStartupCommand() {
20060
+ const nodePath = process.execPath;
20061
+ const scriptPath = process.argv[1];
20062
+ return `"${nodePath}" "${scriptPath}" --transport http`;
20063
+ }
20064
+ function xdgAutostartDir() {
20065
+ const xdg = process.env.XDG_CONFIG_HOME;
20066
+ return path21.join(xdg || path21.join(os14.homedir(), ".config"), "autostart");
20067
+ }
20068
+ function installAutostart() {
20069
+ const platform = process.platform;
20070
+ if (platform === "win32") {
20071
+ const cmd = getStartupCommand().replace(/"/g, '\\"');
20072
+ try {
20073
+ execSync(
20074
+ `reg add "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" /v "${AUTOSTART_NAME}" /t REG_SZ /d "${cmd}" /f`,
20075
+ { stdio: "pipe", timeout: 5e3 }
20076
+ );
20077
+ console.error(`\u81EA\u52A8\u542F\u52A8\u5DF2\u5B89\u88C5 (HKCU\\...\\Run)`);
20078
+ return true;
20079
+ } catch (e) {
20080
+ console.error(`\u5B89\u88C5\u81EA\u52A8\u542F\u52A8\u5931\u8D25: ${e.message}`);
20081
+ return false;
20082
+ }
20083
+ }
20084
+ if (platform === "darwin") {
20085
+ const plistDir = path21.join(os14.homedir(), "Library", "LaunchAgents");
20086
+ const plistPath = path21.join(plistDir, `com.atlisp.mcp.plist`);
20087
+ const cmd = getStartupCommand().replace(/"/g, "&quot;");
20088
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
20089
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
20090
+ <plist version="1.0">
20091
+ <dict>
20092
+ <key>Label</key>
20093
+ <string>com.atlisp.mcp</string>
20094
+ <key>ProgramArguments</key>
20095
+ <array>
20096
+ <string>${process.execPath}</string>
20097
+ <string>${process.argv[1]}</string>
20098
+ <string>--transport</string>
20099
+ <string>http</string>
20100
+ </array>
20101
+ <key>RunAtLoad</key>
20102
+ <true/>
20103
+ <key>KeepAlive</key>
20104
+ <false/>
20105
+ </dict>
20106
+ </plist>`;
20107
+ try {
20108
+ if (!fs17.existsSync(plistDir)) fs17.mkdirSync(plistDir, { recursive: true });
20109
+ fs17.writeFileSync(plistPath, plist, "utf-8");
20110
+ execSync(`launchctl load "${plistPath}"`, { stdio: "pipe", timeout: 5e3 });
20111
+ console.error(`\u81EA\u52A8\u542F\u52A8\u5DF2\u5B89\u88C5 (LaunchAgent)`);
20112
+ return true;
20113
+ } catch (e) {
20114
+ console.error(`\u5B89\u88C5\u81EA\u52A8\u542F\u52A8\u5931\u8D25: ${e.message}`);
20115
+ return false;
20116
+ }
20117
+ }
20118
+ if (platform === "linux") {
20119
+ const dir = xdgAutostartDir();
20120
+ const desktopPath = path21.join(dir, "atlisp-mcp.desktop");
20121
+ const cmd = getStartupCommand();
20122
+ const desktop = `[Desktop Entry]
20123
+ Type=Application
20124
+ Name=@lisp MCP Server
20125
+ Exec=${cmd}
20126
+ Terminal=false
20127
+ X-GNOME-Autostart-enabled=true
20128
+ `;
20129
+ try {
20130
+ if (!fs17.existsSync(dir)) fs17.mkdirSync(dir, { recursive: true });
20131
+ fs17.writeFileSync(desktopPath, desktop, "utf-8");
20132
+ console.error(`\u81EA\u52A8\u542F\u52A8\u5DF2\u5B89\u88C5 (XDG Autostart)`);
20133
+ return true;
20134
+ } catch (e) {
20135
+ console.error(`\u5B89\u88C5\u81EA\u52A8\u542F\u52A8\u5931\u8D25: ${e.message}`);
20136
+ return false;
20137
+ }
20138
+ }
20139
+ console.error(`\u5F53\u524D\u5E73\u53F0 ${platform} \u4E0D\u652F\u6301\u81EA\u52A8\u542F\u52A8`);
20140
+ return false;
20141
+ }
20142
+ function uninstallAutostart() {
20143
+ const platform = process.platform;
20144
+ if (platform === "win32") {
20145
+ try {
20146
+ execSync(
20147
+ `reg delete "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" /v "${AUTOSTART_NAME}" /f`,
20148
+ { stdio: "pipe", timeout: 5e3 }
20149
+ );
20150
+ console.error("\u81EA\u52A8\u542F\u52A8\u5DF2\u5378\u8F7D");
20151
+ return true;
20152
+ } catch (e) {
20153
+ console.error(`\u5378\u8F7D\u81EA\u52A8\u542F\u52A8\u5931\u8D25: ${e.message}`);
20154
+ return false;
20155
+ }
20156
+ }
20157
+ if (platform === "darwin") {
20158
+ const plistPath = path21.join(os14.homedir(), "Library", "LaunchAgents", "com.atlisp.mcp.plist");
20159
+ try {
20160
+ execSync(`launchctl unload "${plistPath}"`, { stdio: "pipe", timeout: 5e3 });
20161
+ fs17.unlinkSync(plistPath);
20162
+ console.error("\u81EA\u52A8\u542F\u52A8\u5DF2\u5378\u8F7D");
20163
+ return true;
20164
+ } catch (e) {
20165
+ console.error(`\u5378\u8F7D\u81EA\u52A8\u542F\u52A8\u5931\u8D25: ${e.message}`);
20166
+ return false;
20167
+ }
20168
+ }
20169
+ if (platform === "linux") {
20170
+ const desktopPath = path21.join(xdgAutostartDir(), "atlisp-mcp.desktop");
20171
+ try {
20172
+ if (fs17.existsSync(desktopPath)) fs17.unlinkSync(desktopPath);
20173
+ console.error("\u81EA\u52A8\u542F\u52A8\u5DF2\u5378\u8F7D");
20174
+ return true;
20175
+ } catch (e) {
20176
+ console.error(`\u5378\u8F7D\u81EA\u52A8\u542F\u52A8\u5931\u8D25: ${e.message}`);
20177
+ return false;
20178
+ }
20179
+ }
20180
+ return false;
20181
+ }
20182
+ function autostartStatus() {
20183
+ const platform = process.platform;
20184
+ if (platform === "win32") {
20185
+ try {
20186
+ const out = execSync(
20187
+ `reg query "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" /v "${AUTOSTART_NAME}"`,
20188
+ { stdio: "pipe", timeout: 5e3, encoding: "utf-8" }
20189
+ );
20190
+ const match = out.match(/(\S+\.js)/);
20191
+ return { installed: true, command: match ? match[1] : out.trim() };
20192
+ } catch {
20193
+ return { installed: false };
20194
+ }
20195
+ }
20196
+ if (platform === "darwin") {
20197
+ const plistPath = path21.join(os14.homedir(), "Library", "LaunchAgents", "com.atlisp.mcp.plist");
20198
+ return { installed: fs17.existsSync(plistPath) };
20199
+ }
20200
+ if (platform === "linux") {
20201
+ const desktopPath = path21.join(xdgAutostartDir(), "atlisp-mcp.desktop");
20202
+ return { installed: fs17.existsSync(desktopPath) };
20203
+ }
20204
+ return { installed: false, error: `${platform} \u4E0D\u652F\u6301\u81EA\u52A8\u542F\u52A8` };
20205
+ }
20206
+
19992
20207
  // src/atlisp-mcp.js
19993
20208
  init_mcp_tools();
19994
20209
  init_function_handlers();
19995
20210
  init_constants();
19996
- var __dirname7 = path20.dirname(fileURLToPath8(import.meta.url));
20211
+ var __dirname7 = path22.dirname(fileURLToPath8(import.meta.url));
19997
20212
  var require3 = createRequire2(import.meta.url);
19998
- var { version: version2 } = require3(path20.join(__dirname7, "..", "package.json"));
20213
+ var { version: version2 } = require3(path22.join(__dirname7, "..", "package.json"));
19999
20214
  var isMcpScript = process.argv[1]?.includes("atlisp-mcp");
20000
20215
  if (isMcpScript) {
20001
20216
  const cliArgs2 = process.argv.slice(2);
@@ -20031,6 +20246,9 @@ Options:
20031
20246
  --api-key <key> API key for authentication
20032
20247
  --client-id <id> Client identifier for remote health report
20033
20248
  --security-level <type> Security level: strict, standard, relaxed (default: strict)
20249
+ --install-autostart Register server to auto-start after user login
20250
+ --uninstall-autostart Remove auto-start registration
20251
+ --autostart-status Check if auto-start is installed
20034
20252
 
20035
20253
  Environment Variables:
20036
20254
  TRANSPORT Transport mode (http/stdio/ws)
@@ -20111,6 +20329,21 @@ For more info: https://atlisp.cn
20111
20329
  process.env.CLIENT_ID = cliArgs2[++i];
20112
20330
  } else if (arg === "--metrics-enabled" && cliArgs2[i + 1]) {
20113
20331
  process.env.METRICS_ENABLED = cliArgs2[++i];
20332
+ } else if (arg === "--install-autostart") {
20333
+ installAutostart();
20334
+ process.exit(0);
20335
+ } else if (arg === "--uninstall-autostart") {
20336
+ uninstallAutostart();
20337
+ process.exit(0);
20338
+ } else if (arg === "--autostart-status") {
20339
+ const s = autostartStatus();
20340
+ if (s.installed) {
20341
+ console.error(`\u81EA\u52A8\u542F\u52A8: \u5DF2\u5B89\u88C5`);
20342
+ if (s.command) console.error(` \u547D\u4EE4: ${s.command}`);
20343
+ } else {
20344
+ console.error(`\u81EA\u52A8\u542F\u52A8: \u672A\u5B89\u88C5`);
20345
+ }
20346
+ process.exit(0);
20114
20347
  }
20115
20348
  }
20116
20349
  }
@@ -20141,9 +20374,9 @@ async function startServer() {
20141
20374
  startPluginWatcher();
20142
20375
  let promptsWatcher = null;
20143
20376
  try {
20144
- const promptsDir = path20.join(__dirname7, "..", "prompts");
20145
- if (fs16.existsSync(promptsDir)) {
20146
- promptsWatcher = fs16.watch(promptsDir, (eventType, filename) => {
20377
+ const promptsDir = path22.join(__dirname7, "..", "prompts");
20378
+ if (fs18.existsSync(promptsDir)) {
20379
+ promptsWatcher = fs18.watch(promptsDir, (eventType, filename) => {
20147
20380
  if (filename && filename.endsWith(".json")) {
20148
20381
  log(`Prompt changed: ${filename}`);
20149
20382
  broadcastPromptsListChanged();
@@ -20191,13 +20424,23 @@ async function startServer() {
20191
20424
  let httpsServer;
20192
20425
  if (useSsl) {
20193
20426
  const sslOptions = {
20194
- key: fs16.readFileSync(config_default.sslKey, "utf-8"),
20195
- cert: fs16.readFileSync(config_default.sslCert, "utf-8")
20427
+ key: fs18.readFileSync(config_default.sslKey, "utf-8"),
20428
+ cert: fs18.readFileSync(config_default.sslCert, "utf-8")
20196
20429
  };
20197
20430
  httpsServer = https.createServer(sslOptions, app);
20198
20431
  }
20199
20432
  const rawServer = httpsServer || http.createServer(app);
20200
20433
  createWebSocketServer(rawServer);
20434
+ const lock = acquirePortLock(config_default.port, config_default.host);
20435
+ if (!lock.ok) {
20436
+ console.error(`
20437
+ \u9519\u8BEF: \u7AEF\u53E3 ${config_default.port} \u5DF2\u88AB\u53E6\u4E00\u4E2A atlisp-mcp \u5B9E\u4F8B\u5360\u7528`);
20438
+ console.error(` PID: ${lock.pid}`);
20439
+ if (lock.started) console.error(` \u542F\u52A8\u65F6\u95F4: ${lock.started}`);
20440
+ console.error(`\u8BF7\u9009\u62E9\u5176\u4ED6\u7AEF\u53E3 (--port) \u6216\u7ED3\u675F\u5360\u7528\u8FDB\u7A0B\u540E\u91CD\u8BD5\u3002
20441
+ `);
20442
+ process.exit(1);
20443
+ }
20201
20444
  const httpServer = rawServer.listen(config_default.port, config_default.host, async () => {
20202
20445
  await initCadConnection();
20203
20446
  const proto = useSsl ? "https" : "http";
@@ -20219,6 +20462,7 @@ async function startServer() {
20219
20462
  removeSessionServer(sid);
20220
20463
  }
20221
20464
  await closeStreamableHttpServer();
20465
+ releasePortLock(config_default.port);
20222
20466
  await cad.disconnect();
20223
20467
  closeLog();
20224
20468
  httpServer.close(() => {
@@ -20229,6 +20473,7 @@ async function startServer() {
20229
20473
  }
20230
20474
  process.on("SIGINT", () => shutdown("SIGINT"));
20231
20475
  process.on("SIGTERM", () => shutdown("SIGTERM"));
20476
+ process.on("exit", () => releasePortLock(config_default.port));
20232
20477
  }
20233
20478
  }
20234
20479
  onDocumentChanged((info) => {
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlisp/mcp",
3
- "version": "1.8.30",
3
+ "version": "1.8.31",
4
4
  "description": "MCP Server for @lisp on CAD,support AutoCAD/GstarCAD/ZWCAD/BricsCAD or CAD platform compatible with AutoLISP",
5
5
  "type": "module",
6
6
  "bin": {