Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@atlisp/mcp

Package Overview
Dependencies
Maintainers
1
Versions
79
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@atlisp/mcp - npm Package Compare versions

Comparing version
1.8.11
to
1.8.12
+241
dist/config.js
import { z } from 'zod';
import fs from 'fs';
import path from 'path';
import os from 'os';
const envSchema = z.object({
PORT: z.string().optional().default('8110'),
HOST: z.string().optional().default('0.0.0.0'),
TRANSPORT: z.enum(['http', 'stdio', 'ws']).optional().default('http'),
MCP_API_KEY: z.string().optional().default(''),
ENABLE_SSE: z.string().optional().default('true'),
MESSAGE_TIMEOUT: z.string().optional().default('60000'),
HEARTBEAT_INTERVAL: z.string().optional().default('30000'),
BUSY_RETRIES: z.string().optional().default('10'),
BUSY_DELAY: z.string().optional().default('500'),
ENABLE_CORS: z.string().optional().default('true'),
CORS_ORIGIN: z.string().optional().default('*'),
RATE_LIMIT_WINDOW: z.string().optional().default('60000'),
RATE_LIMIT_MAX: z.string().optional().default('100'),
RESOURCE_CACHE_TTL: z.string().optional().default('5000'),
FUNCTION_LIB_CACHE_TTL: z.string().optional().default('300000'),
DEBUG: z.string().optional().default('false'),
DEBUG_FILE: z.string().optional().default(''),
FUNCTION_LIB_URL: z.string().optional().default('http://s3.atlisp.cn/json/functions.json'),
CONFIG_FILE: z.string().optional().default(''),
API_KEY_RATE_LIMIT: z.string().optional().default('1000'),
METRICS_ENABLED: z.string().optional().default('true'),
REQUEST_LOG_ENABLED: z.string().optional().default('true'),
SSL_KEY_PATH: z.string().optional().default(''),
SSL_CERT_PATH: z.string().optional().default(''),
LLM_API_KEY: z.string().optional().default(''),
LLM_API_URL: z.string().optional().default('https://api.openai.com/v1/chat/completions'),
LLM_MODEL: z.string().optional().default('gpt-4o-mini'),
LLM_MAX_TOKENS: z.string().optional().default('4096'),
LLM_TEMPERATURE: z.string().optional().default('0.7'),
});
const configSchema = z.object({
port: z.number().optional(),
host: z.string().optional(),
transport: z.enum(['http', 'stdio', 'ws']).optional(),
apiKey: z.string().optional(),
enableSse: z.boolean().optional(),
messageTimeout: z.number().optional(),
heartbeatInterval: z.number().optional(),
busyRetries: z.number().optional(),
busyDelay: z.number().optional(),
enableCors: z.boolean().optional(),
corsOrigin: z.string().optional(),
rateLimitWindow: z.number().optional(),
rateLimitMax: z.number().optional(),
resourceCacheTtl: z.number().optional(),
functionLibCacheTtl: z.number().optional(),
debug: z.boolean().optional(),
debugFile: z.string().optional(),
functionLibUrl: z.string().optional(),
configFile: z.string().optional(),
apiKeyRateLimit: z.number().optional(),
metricsEnabled: z.boolean().optional(),
apiKeys: z.array(z.object({
key: z.string(),
limit: z.number(),
name: z.string().optional(),
role: z.string().optional()
})).optional(),
sslKey: z.string().optional(),
sslCert: z.string().optional(),
llmApiKey: z.string().optional(),
llmApiUrl: z.string().optional(),
llmModel: z.string().optional(),
llmMaxTokens: z.number().optional(),
llmTemperature: z.number().optional(),
maxPoolSize: z.number().optional(),
});
function parseArgs() {
const args = process.argv.slice(2);
const result = {};
for (let i = 0; i < args.length; i++) {
if (args[i] === '--transport' && args[i + 1]) {
result.transport = args[i + 1];
i++;
} else if (args[i] === '--stdio') {
result.transport = 'stdio';
} else if (args[i] === '--ws') {
result.transport = 'ws';
} else if (args[i] === '--port' && args[i + 1]) {
result.port = args[i + 1];
i++;
} else if (args[i] === '--host' && args[i + 1]) {
result.host = args[i + 1];
i++;
} else if (args[i] === '--config' && args[i + 1]) {
result.configFile = args[i + 1];
i++;
} else if (args[i] === '--ssl-key' && args[i + 1]) {
result.sslKey = args[i + 1];
i++;
} else if (args[i] === '--ssl-cert' && args[i + 1]) {
result.sslCert = args[i + 1];
i++;
} else if (args[i] === '--debug') {
result.debug = true;
}
}
return result;
}
export { parseArgs };
function loadConfigFile(filePath) {
if (!filePath || !fs.existsSync(filePath)) return null;
try {
const raw = fs.readFileSync(filePath, 'utf-8');
const parsed = JSON.parse(raw);
const result = configSchema.safeParse(parsed);
if (result.success) {
return result.data;
}
console.error('配置文件格式错误:', result.error);
} catch (e) {
console.error('加载配置文件失败:', e.message);
}
return null;
}
const cliArgs = parseArgs();
const envResult = envSchema.safeParse(process.env);
if (!envResult.success) {
console.error('环境变量配置错误:', envResult.error.flatten().fieldErrors);
process.exit(1);
}
const env = envResult.data;
const configFilePath = env.CONFIG_FILE || cliArgs.configFile ||
path.join(os.homedir(), '.atlisp', 'atlisp.config.json');
const fileConfig = loadConfigFile(configFilePath);
const config = {
port: fileConfig?.port || parseInt(cliArgs.port || env.PORT, 10),
host: fileConfig?.host || cliArgs.host || env.HOST,
transport: fileConfig?.transport ?? cliArgs.transport ?? env.TRANSPORT,
apiKey: fileConfig?.apiKey || env.MCP_API_KEY,
enableSse: fileConfig?.enableSse ?? (env.ENABLE_SSE === 'true'),
messageTimeout: fileConfig?.messageTimeout || parseInt(env.MESSAGE_TIMEOUT, 10),
heartbeatInterval: fileConfig?.heartbeatInterval || parseInt(env.HEARTBEAT_INTERVAL, 10),
busyRetries: fileConfig?.busyRetries || parseInt(env.BUSY_RETRIES, 10),
busyDelay: fileConfig?.busyDelay || parseInt(env.BUSY_DELAY, 10),
enableCors: fileConfig?.enableCors ?? (env.ENABLE_CORS === 'true'),
corsOrigin: fileConfig?.corsOrigin || env.CORS_ORIGIN,
rateLimitWindow: fileConfig?.rateLimitWindow || parseInt(env.RATE_LIMIT_WINDOW, 10),
rateLimitMax: fileConfig?.rateLimitMax || parseInt(env.RATE_LIMIT_MAX, 10),
resourceCacheTtl: fileConfig?.resourceCacheTtl || parseInt(env.RESOURCE_CACHE_TTL, 10),
functionLibCacheTtl: fileConfig?.functionLibCacheTtl || parseInt(env.FUNCTION_LIB_CACHE_TTL, 10),
debug: fileConfig?.debug ?? (env.DEBUG === 'true'),
debugFile: fileConfig?.debugFile || env.DEBUG_FILE,
functionLibUrl: fileConfig?.functionLibUrl || env.FUNCTION_LIB_URL,
configFile: configFilePath,
apiKeyRateLimit: fileConfig?.apiKeyRateLimit || parseInt(env.API_KEY_RATE_LIMIT, 10),
apiKeys: fileConfig?.apiKeys || [],
metricsEnabled: fileConfig?.metricsEnabled ?? (env.METRICS_ENABLED === 'true'),
requestLogEnabled: fileConfig?.requestLogEnabled ?? (env.REQUEST_LOG_ENABLED !== 'false'),
sslKey: fileConfig?.sslKey || env.SSL_KEY_PATH || cliArgs.sslKey,
sslCert: fileConfig?.sslCert || env.SSL_CERT_PATH || cliArgs.sslCert,
llmApiKey: fileConfig?.llmApiKey || env.LLM_API_KEY,
llmApiUrl: fileConfig?.llmApiUrl || env.LLM_API_URL,
llmModel: fileConfig?.llmModel || env.LLM_MODEL,
llmMaxTokens: fileConfig?.llmMaxTokens || parseInt(env.LLM_MAX_TOKENS, 10),
llmTemperature: fileConfig?.llmTemperature || parseFloat(env.LLM_TEMPERATURE),
maxPoolSize: fileConfig?.maxPoolSize || 3,
};
export function validateConfig() {
const errors = [];
if (config.transport === 'http' || config.transport === 'ws') {
if (config.port < 1 || config.port > 65535) {
errors.push(`端口号无效: ${config.port}`);
}
}
if (config.sslKey && !fs.existsSync(config.sslKey)) {
errors.push(`SSL 密钥文件不存在: ${config.sslKey}`);
}
if (config.sslCert && !fs.existsSync(config.sslCert)) {
errors.push(`SSL 证书文件不存在: ${config.sslCert}`);
}
if (config.messageTimeout < 1000) {
errors.push(`消息超时过短: ${config.messageTimeout}ms (最小 1000ms)`);
}
if (config.rateLimitMax < 1) {
errors.push(`速率限制无效: ${config.rateLimitMax}`);
}
if (errors.length > 0) {
console.error('配置验证错误:');
errors.forEach(e => console.error(` - ${e}`));
return false;
}
return true;
}
let configWatchers = [];
if (configFilePath && fs.existsSync(configFilePath)) {
try {
fs.watch(configFilePath, (eventType) => {
if (eventType === 'change') {
const newFileConfig = loadConfigFile(configFilePath);
if (newFileConfig) {
Object.assign(config, newFileConfig);
configWatchers.forEach(fn => fn(config));
}
}
});
} catch (e) { console.error(`Config file watch error: ${e.message}`); }
}
export function getConfig(key) {
return config[key];
}
export function getAllConfig() {
return { ...config };
}
export function reloadConfig() {
const newFileConfig = loadConfigFile(config.configFile);
if (newFileConfig) {
Object.assign(config, newFileConfig);
configWatchers.forEach(fn => fn(config));
return true;
}
return false;
}
export function onConfigChange(callback) {
configWatchers.push(callback);
}
export default config;
import { log, error as logError } from './logger.js';
export const THREAT_BLOCK = 'block';
export const THREAT_WARN = 'warn';
export const THREAT_ALLOW = 'allow';
export const SECURITY_RULES = [
{ re: /\beval\s*[(\s]/i, severity: THREAT_BLOCK, msg: '禁止使用 eval 函数' },
{ re: /\bstartapp\s*[(\s]/i, severity: THREAT_BLOCK, msg: '禁止使用 startapp 函数' },
{ re: /\bvl-bt\b/i, severity: THREAT_BLOCK, msg: '禁止使用 vl-bt 函数' },
{ re: /\bvl-exit-with-value\b/i, severity: THREAT_BLOCK, msg: '禁止使用 vl-exit-with-value' },
{ re: /\bvl-exit-with-error\b/i, severity: THREAT_BLOCK, msg: '禁止使用 vl-exit-with-error' },
{ re: /\bvlax-import-type-library\b/i, severity: THREAT_BLOCK, msg: '禁止导入类型库' },
{ re: /\bvlax-add-cmd\b/i, severity: THREAT_BLOCK, msg: '禁止注册新命令' },
{ re: /\bvla-Delete\b/i, severity: THREAT_BLOCK, msg: '禁止直接删除 vla 对象' },
{ re: /\bvla-Save\b/i, severity: THREAT_BLOCK, msg: '禁止直接保存文档' },
{ re: /\bdirectory\s*[(\s]/i, severity: THREAT_BLOCK, msg: '禁止使用 directory 函数' },
{ re: /\bvla-deletefile\b/i, severity: THREAT_BLOCK, msg: '禁止删除文件' },
{ re: /\bvla-copyfile\b/i, severity: THREAT_BLOCK, msg: '禁止复制文件' },
{ re: /\bvla-movefile\b/i, severity: THREAT_BLOCK, msg: '禁止移动文件' },
{ re: /\bvl-file-copy\s*[(\s]/i, severity: THREAT_BLOCK, msg: '禁止文件复制' },
{ re: /\bvl-file-delete\s*[(\s]/i, severity: THREAT_BLOCK, msg: '禁止文件删除' },
{ re: /\bvl-file-rename\s*[(\s]/i, severity: THREAT_BLOCK, msg: '禁止文件重命名' },
{ re: /\bdos_command_string\s*[(\s]/i, severity: THREAT_BLOCK, msg: '禁止使用 dos_command_string' },
{ re: /\bload\s*[(\s"]/i, severity: THREAT_WARN, msg: 'load 函数可能加载外部代码' },
{ re: /\bcommand\s*["\s]*[_(]?\s*["']?\.?\s*_?(?:quit|exit|close|open|new|recover|audit|purge|wblock|export)/i, severity: THREAT_BLOCK, msg: '禁止使用危险 CAD 命令' },
{ re: /\b(?:read-line|read-char|write-line|write-char)\s*[(\s]/i, severity: THREAT_WARN, msg: '文件 I/O 操作可能影响系统' },
{ re: /\bvla-add\b/i, severity: THREAT_ALLOW, msg: 'vla-add 创建新对象' },
];
export function stripStringsAndComments(code) {
const withoutStrings = code.replace(/"([^"\\]*(?:\\.[^"\\]*)*)"/g, '');
return withoutStrings.replace(/;.*$/gm, '');
}
export function validateLispCodeWorker(code) {
const clean = stripStringsAndComments(code);
for (const { re, msg } of SECURITY_RULES) {
if (re.test(clean)) {
return { valid: false, error: msg };
}
}
return { valid: true };
}
export function checkParens(code) {
let depth = 0;
let inString = false;
for (let i = 0; i < code.length; i++) {
const ch = code[i];
if (inString) {
if (ch === '\\' && i + 1 < code.length) {
i++;
continue;
}
if (ch === '"') {
inString = false;
}
continue;
}
if (ch === '"') {
inString = true;
continue;
}
if (ch === ';') {
while (i < code.length && code[i] !== '\n') i++;
continue;
}
if (ch === '(') depth++;
if (ch === ')') depth--;
if (depth < 0) return { valid: false, error: '多余右括号 )', pos: i };
}
if (depth > 0) return { valid: false, error: `缺少 ${depth} 个右括号 )`, pos: code.length - 1 };
if (depth < 0) return { valid: false, error: `多余 ${-depth} 个左括号 (` };
return { valid: true };
}
import fs from 'fs';
import path from 'path';
import os from 'os';
import config from './config.js';
const DEBUG_FILE = config.debugFile || path.join(os.tmpdir(), 'mcp-server-debug.log');
const MAX_LOG_SIZE = 10 * 1024 * 1024;
const MAX_LOG_FILES = 5;
let stream = null;
function rotateLog() {
try {
if (!fs.existsSync(DEBUG_FILE)) return;
const stat = fs.statSync(DEBUG_FILE);
if (stat.size < MAX_LOG_SIZE) return;
if (stream) { stream.end(); stream = null; }
for (let i = MAX_LOG_FILES - 1; i > 0; i--) {
const oldPath = `${DEBUG_FILE}.${i}`;
if (fs.existsSync(oldPath)) {
try { fs.renameSync(oldPath, `${DEBUG_FILE}.${i + 1}`); } catch (e) { console.error('log rotate rename error:', e.message); }
}
}
try { fs.renameSync(DEBUG_FILE, `${DEBUG_FILE}.1`); } catch (e) { console.error('log rotate rename error:', e.message); }
} catch (e) { console.error('log rotate error:', e.message); }
}
function getStream() {
if (!stream) {
rotateLog();
stream = fs.createWriteStream(DEBUG_FILE, { flags: 'a' });
}
return stream;
}
function formatMessage(level, message, meta = {}) {
const entry = {
timestamp: new Date().toISOString(),
level,
message,
...meta,
pid: process.pid,
hostname: os.hostname(),
};
return JSON.stringify(entry);
}
export function log(message, meta = {}) {
getStream().write(formatMessage('INFO', message, meta) + '\n');
notify('info', message);
if (config.debug) {
console.log(`[INFO] ${message}`, meta);
}
}
export function error(message, meta = {}) {
getStream().write(formatMessage('ERROR', message, meta) + '\n');
notify('error', message);
if (config.debug) {
console.error(`[ERROR] ${message}`, meta);
}
}
export function warn(message, meta = {}) {
getStream().write(formatMessage('WARN', message, meta) + '\n');
notify('warning', message);
if (config.debug) {
console.warn(`[WARN] ${message}`, meta);
}
}
export function debug(message, meta = {}) {
if (config.debug) {
getStream().write(formatMessage('DEBUG', message, meta) + '\n');
}
}
let _logNotify = null;
export function setLoggingNotify(fn) {
_logNotify = fn;
}
export function getLoggingNotify() {
return _logNotify;
}
function notify(level, message) {
if (_logNotify) {
try { _logNotify(level, message); } catch {}
}
}
export function closeLog() {
if (stream) {
stream.end();
stream = null;
}
}
let requestStream = null;
const REQUEST_LOG_FILE = path.join(os.homedir(), '@lisp', 'logs', 'requests.log');
function ensureRequestLogDir() {
const dir = path.dirname(REQUEST_LOG_FILE);
if (!fs.existsSync(dir)) {
try { fs.mkdirSync(dir, { recursive: true }); } catch (e) { console.error('request log mkdir error:', e.message); }
}
}
function rotateRequestLog() {
try {
if (!fs.existsSync(REQUEST_LOG_FILE)) return;
const stat = fs.statSync(REQUEST_LOG_FILE);
if (stat.size < 10 * 1024 * 1024) return;
if (requestStream) { requestStream.end(); requestStream = null; }
for (let i = 5; i > 0; i--) {
const oldPath = `${REQUEST_LOG_FILE}.${i}`;
if (fs.existsSync(oldPath)) {
try { fs.renameSync(oldPath, `${REQUEST_LOG_FILE}.${i + 1}`); } catch (e) { console.error('request log rotate error:', e.message); }
}
}
try { fs.renameSync(REQUEST_LOG_FILE, `${REQUEST_LOG_FILE}.1`); } catch (e) { console.error('request log rotate error:', e.message); }
} catch (e) { console.error('request log rotate error:', e.message); }
}
export function logRequest(req) {
if (!config.requestLogEnabled) return;
try {
ensureRequestLogDir();
rotateRequestLog();
if (!requestStream) {
requestStream = fs.createWriteStream(REQUEST_LOG_FILE, { flags: 'a' });
}
const entry = {
timestamp: new Date().toISOString(),
method: req.method,
path: req.path,
query: req.query,
ip: req.ip || req.connection?.remoteAddress,
userAgent: req.get('user-agent'),
};
requestStream.write(JSON.stringify(entry) + '\n');
} catch (e) { console.error('request log error:', e.message); }
}
export function logResponse(req, res, duration) {
if (!config.requestLogEnabled) return;
try {
ensureRequestLogDir();
if (!requestStream) {
requestStream = fs.createWriteStream(REQUEST_LOG_FILE, { flags: 'a' });
}
const entry = {
timestamp: new Date().toISOString(),
method: req.method,
path: req.path,
statusCode: res.statusCode,
duration: `${duration}ms`,
};
requestStream.write(JSON.stringify(entry) + '\n');
} catch (e) { console.error('request log error:', e.message); }
}
+6
-66

@@ -7,2 +7,3 @@ #!/usr/bin/env node

import crypto from 'crypto';
import { validateLispCodeWorker, checkParens } from './lisp-security.js';

@@ -114,4 +115,4 @@ const BUSY_RETRIES = parseInt(process.env.BUSY_RETRIES || '10', 10);

} catch (Exception ex) {
System.Diagnostics.Debug.WriteLine("Create COM " + platforms[i] + " error: " + ex.Message);
}
System.Diagnostics.Debug.WriteLine("cadLaunch create " + progIds[i] + " error: " + ex.Message);
}
}

@@ -475,65 +476,4 @@

const DANGEROUS_PATTERNS = [
{ re: /\beval\s*[(\s]/i, msg: '禁止使用 eval 函数' },
{ re: /\bload\s*[(\s"]/i, msg: '禁止使用 load 函数' },
{ re: /\bstartapp\s*[(\s]/i, msg: '禁止使用 startapp 函数' },
{ re: /\bvl-bt\b/i, msg: '禁止使用 vl-bt 函数' },
{ re: /\bvl-exit-with-value\b/i, msg: '禁止使用 vl-exit-with-value' },
{ re: /\bvl-exit-with-error\b/i, msg: '禁止使用 vl-exit-with-error' },
{ re: /\bvlax-import-type-library\b/i, msg: '禁止导入类型库' },
{ re: /\bvlax-add-cmd\b/i, msg: '禁止注册新命令' },
{ re: /\bvla-Delete\b/i, msg: '禁止直接删除 vla 对象' },
{ re: /\bvla-Save\b/i, msg: '禁止直接保存文档' },
{ re: /\bcommand\s*["\s]*[_(]?\s*["']?\.?\s*_?(?:quit|exit|close|open|new|recover|audit|purge|wblock|export)/i, msg: '禁止使用危险 CAD 命令' },
{ re: /\bdirectory\s*[(\s]/i, msg: '禁止使用 directory 函数' },
{ re: /\b(?:read-line|read-char|write-line|write-char)\s*[(\s]/i, msg: '禁止直接文件 I/O' },
{ re: /\bvla-deletefile\b/i, msg: '禁止删除文件' },
{ re: /\bvla-copyfile\b/i, msg: '禁止复制文件' },
{ re: /\bvla-movefile\b/i, msg: '禁止移动文件' },
{ re: /\bdos_command_string\s*[(\s]/i, msg: '禁止使用 dos_command_string' },
];
export { checkParens } from './lisp-security.js';
function validateLispCode(code) {
const withoutStrings = code.replace(/"([^"\\]*(?:\\.[^"\\]*)*)"/g, '');
const withoutComments = withoutStrings.replace(/;.*$/gm, '');
for (const { re, msg } of DANGEROUS_PATTERNS) {
if (re.test(withoutComments)) {
return { valid: false, error: msg };
}
}
return { valid: true };
}
export function checkParens(code) {
let depth = 0;
let inString = false;
for (let i = 0; i < code.length; i++) {
const ch = code[i];
if (inString) {
if (ch === '\\' && i + 1 < code.length) {
i++;
continue;
}
if (ch === '"') {
inString = false;
}
continue;
}
if (ch === '"') {
inString = true;
continue;
}
if (ch === ';') {
while (i < code.length && code[i] !== '\n') i++;
continue;
}
if (ch === '(') depth++;
if (ch === ')') depth--;
if (depth < 0) return { valid: false, error: '多余右括号 )', pos: i };
}
if (depth > 0) return { valid: false, error: `缺少 ${depth} 个右括号 )`, pos: code.length - 1 };
if (depth < 0) return { valid: false, error: `多余 ${-depth} 个左括号 (` };
return { valid: true };
}
async function connectToPlatform(targetPlatform) {

@@ -615,3 +555,3 @@ return new Promise((resolve, reject) => {

}
const injectionCheck = validateLispCode(msg.code);
const injectionCheck = validateLispCodeWorker(msg.code);
if (!injectionCheck.valid) {

@@ -669,3 +609,3 @@ return { success: false, error: `安全限制: ${injectionCheck.error}` };

}
const injectionCheck = validateLispCode(msg.code);
const injectionCheck = validateLispCodeWorker(msg.code);
if (!injectionCheck.valid) {

@@ -672,0 +612,0 @@ return { success: false, error: `安全限制: ${injectionCheck.error}` };

{
"name": "@atlisp/mcp",
"version": "1.8.11",
"version": "1.8.12",
"description": "MCP Server for @lisp on CAD,support AutoCAD/GstarCAD/ZWCAD/BricsCAD or CAD platform compatible with AutoLISP",

@@ -21,3 +21,3 @@ "type": "module",

"build:main": "esbuild src/atlisp-mcp.js --bundle --platform=node --target=node18 --format=esm --outdir=dist --external:edge-js --packages=external --banner:js=\"#!/usr/bin/env node\"",
"build:worker": "node -e \"require('fs').copyFileSync('src/cad-worker.js','dist/cad-worker.js')\"",
"build:worker": "node -e \"const fs=require('fs');for(const f of ['cad-worker.js','lisp-security.js','logger.js','config.js'])fs.copyFileSync('src/'+f,'dist/'+f)\"",
"prepublishOnly": "npm run build",

@@ -24,0 +24,0 @@ "test": "vitest run",

Sorry, the diff of this file is too big to display