@atlisp/mcp 1.8.11 → 1.8.13
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 +13114 -10200
- package/dist/cad-worker.js +6 -66
- package/dist/config.js +241 -0
- package/dist/html/api-docs.html +66 -0
- package/dist/html/debug.html +75 -0
- package/dist/html/playground.html +347 -0
- package/dist/lisp-security.js +76 -0
- package/dist/logger.js +163 -0
- package/package.json +2 -2
package/dist/cad-worker.js
CHANGED
|
@@ -4,6 +4,7 @@ import process from 'process';
|
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import crypto from 'crypto';
|
|
7
|
+
import { validateLispCodeWorker, checkParens } from './lisp-security.js';
|
|
7
8
|
|
|
8
9
|
const BUSY_RETRIES = parseInt(process.env.BUSY_RETRIES || '10', 10);
|
|
9
10
|
const BUSY_DELAY = parseInt(process.env.BUSY_DELAY || '500', 10);
|
|
@@ -110,8 +111,8 @@ const cadLaunch = edge.func(function() {/*
|
|
|
110
111
|
}
|
|
111
112
|
}
|
|
112
113
|
} catch (Exception ex) {
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
System.Diagnostics.Debug.WriteLine("cadLaunch create " + progIds[i] + " error: " + ex.Message);
|
|
115
|
+
}
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
return new { success = false, error = "No CAD found" };
|
|
@@ -470,68 +471,7 @@ async function waitForIdle(platform) {
|
|
|
470
471
|
return false;
|
|
471
472
|
}
|
|
472
473
|
|
|
473
|
-
|
|
474
|
-
{ re: /\beval\s*[(\s]/i, msg: '禁止使用 eval 函数' },
|
|
475
|
-
{ re: /\bload\s*[(\s"]/i, msg: '禁止使用 load 函数' },
|
|
476
|
-
{ re: /\bstartapp\s*[(\s]/i, msg: '禁止使用 startapp 函数' },
|
|
477
|
-
{ re: /\bvl-bt\b/i, msg: '禁止使用 vl-bt 函数' },
|
|
478
|
-
{ re: /\bvl-exit-with-value\b/i, msg: '禁止使用 vl-exit-with-value' },
|
|
479
|
-
{ re: /\bvl-exit-with-error\b/i, msg: '禁止使用 vl-exit-with-error' },
|
|
480
|
-
{ re: /\bvlax-import-type-library\b/i, msg: '禁止导入类型库' },
|
|
481
|
-
{ re: /\bvlax-add-cmd\b/i, msg: '禁止注册新命令' },
|
|
482
|
-
{ re: /\bvla-Delete\b/i, msg: '禁止直接删除 vla 对象' },
|
|
483
|
-
{ re: /\bvla-Save\b/i, msg: '禁止直接保存文档' },
|
|
484
|
-
{ re: /\bcommand\s*["\s]*[_(]?\s*["']?\.?\s*_?(?:quit|exit|close|open|new|recover|audit|purge|wblock|export)/i, msg: '禁止使用危险 CAD 命令' },
|
|
485
|
-
{ re: /\bdirectory\s*[(\s]/i, msg: '禁止使用 directory 函数' },
|
|
486
|
-
{ re: /\b(?:read-line|read-char|write-line|write-char)\s*[(\s]/i, msg: '禁止直接文件 I/O' },
|
|
487
|
-
{ re: /\bvla-deletefile\b/i, msg: '禁止删除文件' },
|
|
488
|
-
{ re: /\bvla-copyfile\b/i, msg: '禁止复制文件' },
|
|
489
|
-
{ re: /\bvla-movefile\b/i, msg: '禁止移动文件' },
|
|
490
|
-
{ re: /\bdos_command_string\s*[(\s]/i, msg: '禁止使用 dos_command_string' },
|
|
491
|
-
];
|
|
492
|
-
|
|
493
|
-
function validateLispCode(code) {
|
|
494
|
-
const withoutStrings = code.replace(/"([^"\\]*(?:\\.[^"\\]*)*)"/g, '');
|
|
495
|
-
const withoutComments = withoutStrings.replace(/;.*$/gm, '');
|
|
496
|
-
for (const { re, msg } of DANGEROUS_PATTERNS) {
|
|
497
|
-
if (re.test(withoutComments)) {
|
|
498
|
-
return { valid: false, error: msg };
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
return { valid: true };
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
export function checkParens(code) {
|
|
505
|
-
let depth = 0;
|
|
506
|
-
let inString = false;
|
|
507
|
-
for (let i = 0; i < code.length; i++) {
|
|
508
|
-
const ch = code[i];
|
|
509
|
-
if (inString) {
|
|
510
|
-
if (ch === '\\' && i + 1 < code.length) {
|
|
511
|
-
i++;
|
|
512
|
-
continue;
|
|
513
|
-
}
|
|
514
|
-
if (ch === '"') {
|
|
515
|
-
inString = false;
|
|
516
|
-
}
|
|
517
|
-
continue;
|
|
518
|
-
}
|
|
519
|
-
if (ch === '"') {
|
|
520
|
-
inString = true;
|
|
521
|
-
continue;
|
|
522
|
-
}
|
|
523
|
-
if (ch === ';') {
|
|
524
|
-
while (i < code.length && code[i] !== '\n') i++;
|
|
525
|
-
continue;
|
|
526
|
-
}
|
|
527
|
-
if (ch === '(') depth++;
|
|
528
|
-
if (ch === ')') depth--;
|
|
529
|
-
if (depth < 0) return { valid: false, error: '多余右括号 )', pos: i };
|
|
530
|
-
}
|
|
531
|
-
if (depth > 0) return { valid: false, error: `缺少 ${depth} 个右括号 )`, pos: code.length - 1 };
|
|
532
|
-
if (depth < 0) return { valid: false, error: `多余 ${-depth} 个左括号 (` };
|
|
533
|
-
return { valid: true };
|
|
534
|
-
}
|
|
474
|
+
export { checkParens } from './lisp-security.js';
|
|
535
475
|
|
|
536
476
|
async function connectToPlatform(targetPlatform) {
|
|
537
477
|
return new Promise((resolve, reject) => {
|
|
@@ -609,7 +549,7 @@ async function handleMessage(msg) {
|
|
|
609
549
|
if (!parenCheck.valid) {
|
|
610
550
|
return { success: false, error: `括号错误: ${parenCheck.error} (位置 ${parenCheck.pos})` };
|
|
611
551
|
}
|
|
612
|
-
const injectionCheck =
|
|
552
|
+
const injectionCheck = validateLispCodeWorker(msg.code);
|
|
613
553
|
if (!injectionCheck.valid) {
|
|
614
554
|
return { success: false, error: `安全限制: ${injectionCheck.error}` };
|
|
615
555
|
}
|
|
@@ -662,7 +602,7 @@ async function handleMessage(msg) {
|
|
|
662
602
|
if (!parenCheck.valid) {
|
|
663
603
|
return { success: false, error: `括号错误: ${parenCheck.error} (位置 ${parenCheck.pos})` };
|
|
664
604
|
}
|
|
665
|
-
const injectionCheck =
|
|
605
|
+
const injectionCheck = validateLispCodeWorker(msg.code);
|
|
666
606
|
if (!injectionCheck.valid) {
|
|
667
607
|
return { success: false, error: `安全限制: ${injectionCheck.error}` };
|
|
668
608
|
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
|
|
6
|
+
const envSchema = z.object({
|
|
7
|
+
PORT: z.string().optional().default('8110'),
|
|
8
|
+
HOST: z.string().optional().default('0.0.0.0'),
|
|
9
|
+
TRANSPORT: z.enum(['http', 'stdio', 'ws']).optional().default('http'),
|
|
10
|
+
MCP_API_KEY: z.string().optional().default(''),
|
|
11
|
+
ENABLE_SSE: z.string().optional().default('true'),
|
|
12
|
+
MESSAGE_TIMEOUT: z.string().optional().default('60000'),
|
|
13
|
+
HEARTBEAT_INTERVAL: z.string().optional().default('30000'),
|
|
14
|
+
BUSY_RETRIES: z.string().optional().default('10'),
|
|
15
|
+
BUSY_DELAY: z.string().optional().default('500'),
|
|
16
|
+
ENABLE_CORS: z.string().optional().default('true'),
|
|
17
|
+
CORS_ORIGIN: z.string().optional().default('*'),
|
|
18
|
+
RATE_LIMIT_WINDOW: z.string().optional().default('60000'),
|
|
19
|
+
RATE_LIMIT_MAX: z.string().optional().default('100'),
|
|
20
|
+
RESOURCE_CACHE_TTL: z.string().optional().default('5000'),
|
|
21
|
+
FUNCTION_LIB_CACHE_TTL: z.string().optional().default('300000'),
|
|
22
|
+
DEBUG: z.string().optional().default('false'),
|
|
23
|
+
DEBUG_FILE: z.string().optional().default(''),
|
|
24
|
+
FUNCTION_LIB_URL: z.string().optional().default('http://s3.atlisp.cn/json/functions.json'),
|
|
25
|
+
CONFIG_FILE: z.string().optional().default(''),
|
|
26
|
+
API_KEY_RATE_LIMIT: z.string().optional().default('1000'),
|
|
27
|
+
METRICS_ENABLED: z.string().optional().default('true'),
|
|
28
|
+
REQUEST_LOG_ENABLED: z.string().optional().default('true'),
|
|
29
|
+
SSL_KEY_PATH: z.string().optional().default(''),
|
|
30
|
+
SSL_CERT_PATH: z.string().optional().default(''),
|
|
31
|
+
LLM_API_KEY: z.string().optional().default(''),
|
|
32
|
+
LLM_API_URL: z.string().optional().default('https://api.openai.com/v1/chat/completions'),
|
|
33
|
+
LLM_MODEL: z.string().optional().default('gpt-4o-mini'),
|
|
34
|
+
LLM_MAX_TOKENS: z.string().optional().default('4096'),
|
|
35
|
+
LLM_TEMPERATURE: z.string().optional().default('0.7'),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const configSchema = z.object({
|
|
39
|
+
port: z.number().optional(),
|
|
40
|
+
host: z.string().optional(),
|
|
41
|
+
transport: z.enum(['http', 'stdio', 'ws']).optional(),
|
|
42
|
+
apiKey: z.string().optional(),
|
|
43
|
+
enableSse: z.boolean().optional(),
|
|
44
|
+
messageTimeout: z.number().optional(),
|
|
45
|
+
heartbeatInterval: z.number().optional(),
|
|
46
|
+
busyRetries: z.number().optional(),
|
|
47
|
+
busyDelay: z.number().optional(),
|
|
48
|
+
enableCors: z.boolean().optional(),
|
|
49
|
+
corsOrigin: z.string().optional(),
|
|
50
|
+
rateLimitWindow: z.number().optional(),
|
|
51
|
+
rateLimitMax: z.number().optional(),
|
|
52
|
+
resourceCacheTtl: z.number().optional(),
|
|
53
|
+
functionLibCacheTtl: z.number().optional(),
|
|
54
|
+
debug: z.boolean().optional(),
|
|
55
|
+
debugFile: z.string().optional(),
|
|
56
|
+
functionLibUrl: z.string().optional(),
|
|
57
|
+
configFile: z.string().optional(),
|
|
58
|
+
apiKeyRateLimit: z.number().optional(),
|
|
59
|
+
metricsEnabled: z.boolean().optional(),
|
|
60
|
+
apiKeys: z.array(z.object({
|
|
61
|
+
key: z.string(),
|
|
62
|
+
limit: z.number(),
|
|
63
|
+
name: z.string().optional(),
|
|
64
|
+
role: z.string().optional()
|
|
65
|
+
})).optional(),
|
|
66
|
+
sslKey: z.string().optional(),
|
|
67
|
+
sslCert: z.string().optional(),
|
|
68
|
+
llmApiKey: z.string().optional(),
|
|
69
|
+
llmApiUrl: z.string().optional(),
|
|
70
|
+
llmModel: z.string().optional(),
|
|
71
|
+
llmMaxTokens: z.number().optional(),
|
|
72
|
+
llmTemperature: z.number().optional(),
|
|
73
|
+
maxPoolSize: z.number().optional(),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
function parseArgs() {
|
|
77
|
+
const args = process.argv.slice(2);
|
|
78
|
+
const result = {};
|
|
79
|
+
for (let i = 0; i < args.length; i++) {
|
|
80
|
+
if (args[i] === '--transport' && args[i + 1]) {
|
|
81
|
+
result.transport = args[i + 1];
|
|
82
|
+
i++;
|
|
83
|
+
} else if (args[i] === '--stdio') {
|
|
84
|
+
result.transport = 'stdio';
|
|
85
|
+
} else if (args[i] === '--ws') {
|
|
86
|
+
result.transport = 'ws';
|
|
87
|
+
} else if (args[i] === '--port' && args[i + 1]) {
|
|
88
|
+
result.port = args[i + 1];
|
|
89
|
+
i++;
|
|
90
|
+
} else if (args[i] === '--host' && args[i + 1]) {
|
|
91
|
+
result.host = args[i + 1];
|
|
92
|
+
i++;
|
|
93
|
+
} else if (args[i] === '--config' && args[i + 1]) {
|
|
94
|
+
result.configFile = args[i + 1];
|
|
95
|
+
i++;
|
|
96
|
+
} else if (args[i] === '--ssl-key' && args[i + 1]) {
|
|
97
|
+
result.sslKey = args[i + 1];
|
|
98
|
+
i++;
|
|
99
|
+
} else if (args[i] === '--ssl-cert' && args[i + 1]) {
|
|
100
|
+
result.sslCert = args[i + 1];
|
|
101
|
+
i++;
|
|
102
|
+
} else if (args[i] === '--debug') {
|
|
103
|
+
result.debug = true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export { parseArgs };
|
|
110
|
+
|
|
111
|
+
function loadConfigFile(filePath) {
|
|
112
|
+
if (!filePath || !fs.existsSync(filePath)) return null;
|
|
113
|
+
try {
|
|
114
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
115
|
+
const parsed = JSON.parse(raw);
|
|
116
|
+
const result = configSchema.safeParse(parsed);
|
|
117
|
+
if (result.success) {
|
|
118
|
+
return result.data;
|
|
119
|
+
}
|
|
120
|
+
console.error('配置文件格式错误:', result.error);
|
|
121
|
+
} catch (e) {
|
|
122
|
+
console.error('加载配置文件失败:', e.message);
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const cliArgs = parseArgs();
|
|
128
|
+
const envResult = envSchema.safeParse(process.env);
|
|
129
|
+
|
|
130
|
+
if (!envResult.success) {
|
|
131
|
+
console.error('环境变量配置错误:', envResult.error.flatten().fieldErrors);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const env = envResult.data;
|
|
136
|
+
|
|
137
|
+
const configFilePath = env.CONFIG_FILE || cliArgs.configFile ||
|
|
138
|
+
path.join(os.homedir(), '.atlisp', 'atlisp.config.json');
|
|
139
|
+
|
|
140
|
+
const fileConfig = loadConfigFile(configFilePath);
|
|
141
|
+
|
|
142
|
+
const config = {
|
|
143
|
+
port: fileConfig?.port || parseInt(cliArgs.port || env.PORT, 10),
|
|
144
|
+
host: fileConfig?.host || cliArgs.host || env.HOST,
|
|
145
|
+
transport: fileConfig?.transport ?? cliArgs.transport ?? env.TRANSPORT,
|
|
146
|
+
apiKey: fileConfig?.apiKey || env.MCP_API_KEY,
|
|
147
|
+
enableSse: fileConfig?.enableSse ?? (env.ENABLE_SSE === 'true'),
|
|
148
|
+
messageTimeout: fileConfig?.messageTimeout || parseInt(env.MESSAGE_TIMEOUT, 10),
|
|
149
|
+
heartbeatInterval: fileConfig?.heartbeatInterval || parseInt(env.HEARTBEAT_INTERVAL, 10),
|
|
150
|
+
busyRetries: fileConfig?.busyRetries || parseInt(env.BUSY_RETRIES, 10),
|
|
151
|
+
busyDelay: fileConfig?.busyDelay || parseInt(env.BUSY_DELAY, 10),
|
|
152
|
+
enableCors: fileConfig?.enableCors ?? (env.ENABLE_CORS === 'true'),
|
|
153
|
+
corsOrigin: fileConfig?.corsOrigin || env.CORS_ORIGIN,
|
|
154
|
+
rateLimitWindow: fileConfig?.rateLimitWindow || parseInt(env.RATE_LIMIT_WINDOW, 10),
|
|
155
|
+
rateLimitMax: fileConfig?.rateLimitMax || parseInt(env.RATE_LIMIT_MAX, 10),
|
|
156
|
+
resourceCacheTtl: fileConfig?.resourceCacheTtl || parseInt(env.RESOURCE_CACHE_TTL, 10),
|
|
157
|
+
functionLibCacheTtl: fileConfig?.functionLibCacheTtl || parseInt(env.FUNCTION_LIB_CACHE_TTL, 10),
|
|
158
|
+
debug: fileConfig?.debug ?? (env.DEBUG === 'true'),
|
|
159
|
+
debugFile: fileConfig?.debugFile || env.DEBUG_FILE,
|
|
160
|
+
functionLibUrl: fileConfig?.functionLibUrl || env.FUNCTION_LIB_URL,
|
|
161
|
+
configFile: configFilePath,
|
|
162
|
+
apiKeyRateLimit: fileConfig?.apiKeyRateLimit || parseInt(env.API_KEY_RATE_LIMIT, 10),
|
|
163
|
+
apiKeys: fileConfig?.apiKeys || [],
|
|
164
|
+
metricsEnabled: fileConfig?.metricsEnabled ?? (env.METRICS_ENABLED === 'true'),
|
|
165
|
+
requestLogEnabled: fileConfig?.requestLogEnabled ?? (env.REQUEST_LOG_ENABLED !== 'false'),
|
|
166
|
+
sslKey: fileConfig?.sslKey || env.SSL_KEY_PATH || cliArgs.sslKey,
|
|
167
|
+
sslCert: fileConfig?.sslCert || env.SSL_CERT_PATH || cliArgs.sslCert,
|
|
168
|
+
llmApiKey: fileConfig?.llmApiKey || env.LLM_API_KEY,
|
|
169
|
+
llmApiUrl: fileConfig?.llmApiUrl || env.LLM_API_URL,
|
|
170
|
+
llmModel: fileConfig?.llmModel || env.LLM_MODEL,
|
|
171
|
+
llmMaxTokens: fileConfig?.llmMaxTokens || parseInt(env.LLM_MAX_TOKENS, 10),
|
|
172
|
+
llmTemperature: fileConfig?.llmTemperature || parseFloat(env.LLM_TEMPERATURE),
|
|
173
|
+
maxPoolSize: fileConfig?.maxPoolSize || 3,
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
export function validateConfig() {
|
|
177
|
+
const errors = [];
|
|
178
|
+
if (config.transport === 'http' || config.transport === 'ws') {
|
|
179
|
+
if (config.port < 1 || config.port > 65535) {
|
|
180
|
+
errors.push(`端口号无效: ${config.port}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (config.sslKey && !fs.existsSync(config.sslKey)) {
|
|
184
|
+
errors.push(`SSL 密钥文件不存在: ${config.sslKey}`);
|
|
185
|
+
}
|
|
186
|
+
if (config.sslCert && !fs.existsSync(config.sslCert)) {
|
|
187
|
+
errors.push(`SSL 证书文件不存在: ${config.sslCert}`);
|
|
188
|
+
}
|
|
189
|
+
if (config.messageTimeout < 1000) {
|
|
190
|
+
errors.push(`消息超时过短: ${config.messageTimeout}ms (最小 1000ms)`);
|
|
191
|
+
}
|
|
192
|
+
if (config.rateLimitMax < 1) {
|
|
193
|
+
errors.push(`速率限制无效: ${config.rateLimitMax}`);
|
|
194
|
+
}
|
|
195
|
+
if (errors.length > 0) {
|
|
196
|
+
console.error('配置验证错误:');
|
|
197
|
+
errors.forEach(e => console.error(` - ${e}`));
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
let configWatchers = [];
|
|
204
|
+
|
|
205
|
+
if (configFilePath && fs.existsSync(configFilePath)) {
|
|
206
|
+
try {
|
|
207
|
+
fs.watch(configFilePath, (eventType) => {
|
|
208
|
+
if (eventType === 'change') {
|
|
209
|
+
const newFileConfig = loadConfigFile(configFilePath);
|
|
210
|
+
if (newFileConfig) {
|
|
211
|
+
Object.assign(config, newFileConfig);
|
|
212
|
+
configWatchers.forEach(fn => fn(config));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
} catch (e) { console.error(`Config file watch error: ${e.message}`); }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function getConfig(key) {
|
|
220
|
+
return config[key];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function getAllConfig() {
|
|
224
|
+
return { ...config };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function reloadConfig() {
|
|
228
|
+
const newFileConfig = loadConfigFile(config.configFile);
|
|
229
|
+
if (newFileConfig) {
|
|
230
|
+
Object.assign(config, newFileConfig);
|
|
231
|
+
configWatchers.forEach(fn => fn(config));
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function onConfigChange(callback) {
|
|
238
|
+
configWatchers.push(callback);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export default config;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>@lisp MCP Server API Docs</title>
|
|
5
|
+
<style>
|
|
6
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 900px; margin: 0 auto; padding: 20px; }
|
|
7
|
+
h1 { color: #333; }
|
|
8
|
+
h2 { color: #666; margin-top: 30px; }
|
|
9
|
+
.endpoint { background: #f5f5f5; padding: 15px; margin: 10px 0; border-radius: 5px; }
|
|
10
|
+
.method { display: inline-block; padding: 3px 8px; border-radius: 3px; font-weight: bold; font-size: 12px; }
|
|
11
|
+
.get { background: #61affe; color: white; }
|
|
12
|
+
.post { background: #49cc90; color: white; }
|
|
13
|
+
code { background: #eee; padding: 2px 5px; border-radius: 3px; }
|
|
14
|
+
.info { background: #f0f0f0; padding: 10px; margin: 10px 0; }
|
|
15
|
+
</style>
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<h1>@lisp MCP Server API</h1>
|
|
19
|
+
<div class="info">
|
|
20
|
+
<strong>Version:</strong> __VERSION__ |
|
|
21
|
+
<strong>Tools:</strong> __TOOLS_COUNT__ |
|
|
22
|
+
<strong>Resources:</strong> 11
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<h2>Endpoints</h2>
|
|
26
|
+
|
|
27
|
+
<div class="endpoint">
|
|
28
|
+
<span class="method get">GET</span> <code>/health</code> - Health check
|
|
29
|
+
</div>
|
|
30
|
+
<div class="endpoint">
|
|
31
|
+
<span class="method get">GET</span> <code>/metrics</code> - Prometheus metrics
|
|
32
|
+
</div>
|
|
33
|
+
<div class="endpoint">
|
|
34
|
+
<span class="method post">POST</span> <code>/mcp</code> - MCP JSON-RPC endpoint
|
|
35
|
+
</div>
|
|
36
|
+
<div class="endpoint">
|
|
37
|
+
<span class="method get">GET</span> <code>/sse</code> - Server-Sent Events
|
|
38
|
+
</div>
|
|
39
|
+
<div class="endpoint">
|
|
40
|
+
<span class="method post">POST</span> <code>/message</code> - SSE message
|
|
41
|
+
</div>
|
|
42
|
+
<div class="endpoint">
|
|
43
|
+
<span class="method get">GET</span> <code>/debug</code> - Debug panel
|
|
44
|
+
</div>
|
|
45
|
+
<div class="endpoint">
|
|
46
|
+
<span class="method post">POST</span> <code>/config/reload</code> - Reload configuration
|
|
47
|
+
</div>
|
|
48
|
+
<div class="endpoint">
|
|
49
|
+
<span class="method get">GET</span> <code>/rate-limit/status</code> - Rate limit status
|
|
50
|
+
</div>
|
|
51
|
+
<div class="endpoint">
|
|
52
|
+
<span class="method post">POST</span> <code>/rate-limit/reset</code> - Reset rate limit
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<h2>Quick Start</h2>
|
|
56
|
+
<pre>
|
|
57
|
+
# Health check
|
|
58
|
+
curl http://localhost:8110/health
|
|
59
|
+
|
|
60
|
+
# Call MCP tool
|
|
61
|
+
curl -X POST http://localhost:8110/mcp \
|
|
62
|
+
-H "Content-Type: application/json" \
|
|
63
|
+
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_cad_info","arguments":{}}}'
|
|
64
|
+
</pre>
|
|
65
|
+
</body>
|
|
66
|
+
</html>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>@lisp MCP Debug Panel</title>
|
|
5
|
+
<style>
|
|
6
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; background: #1a1a2e; color: #eee; }
|
|
7
|
+
h1 { color: #00d4ff; }
|
|
8
|
+
h2 { color: #00d4ff; border-bottom: 1px solid #333; padding-bottom: 10px; }
|
|
9
|
+
.card { background: #16213e; padding: 20px; margin: 10px 0; border-radius: 8px; }
|
|
10
|
+
.stat { display: inline-block; margin: 10px 20px; }
|
|
11
|
+
.stat-label { color: #888; font-size: 12px; }
|
|
12
|
+
.stat-value { font-size: 24px; color: #00d4ff; }
|
|
13
|
+
.status-ok { color: #49cc90; }
|
|
14
|
+
.status-error { color: #f93e3e; }
|
|
15
|
+
table { width: 100%; border-collapse: collapse; }
|
|
16
|
+
th, td { padding: 10px; text-align: left; border-bottom: 1px solid #333; }
|
|
17
|
+
th { color: #888; }
|
|
18
|
+
code { background: #0f0f23; padding: 3px 8px; border-radius: 3px; color: #00d4ff; }
|
|
19
|
+
.refresh { background: #00d4ff; color: #1a1a2e; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin: 10px 0; }
|
|
20
|
+
</style>
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
<h1>🛠️ @lisp MCP Debug Panel</h1>
|
|
24
|
+
<button class="refresh" onclick="location.reload()">🔄 Refresh</button>
|
|
25
|
+
|
|
26
|
+
<h2>System</h2>
|
|
27
|
+
<div class="card">
|
|
28
|
+
<div class="stat">
|
|
29
|
+
<div class="stat-label">Uptime</div>
|
|
30
|
+
<div class="stat-value">__UPTIME_MIN__m</div>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="stat">
|
|
33
|
+
<div class="stat-label">Memory RSS</div>
|
|
34
|
+
<div class="stat-value">__MEMORY_RSS_MB__ MB</div>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="stat">
|
|
37
|
+
<div class="stat-label">Heap Used</div>
|
|
38
|
+
<div class="stat-value">__HEAP_USED_MB__ MB</div>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="stat">
|
|
41
|
+
<div class="stat-label">MCP Sessions</div>
|
|
42
|
+
<div class="stat-value">__SESSIONS_COUNT__</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<h2>CAD Status</h2>
|
|
47
|
+
<div class="card">
|
|
48
|
+
<span class="__CAD_STATUS_CLASS__">
|
|
49
|
+
__CAD_STATUS_TEXT__
|
|
50
|
+
</span>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<h2>Endpoints</h2>
|
|
54
|
+
<div class="card">
|
|
55
|
+
<table>
|
|
56
|
+
<tr><th>Endpoint</th><th>Method</th><th>Description</th></tr>
|
|
57
|
+
<tr><td><code>/health</code></td><td>GET</td><td>Health check</td></tr>
|
|
58
|
+
<tr><td><code>/metrics</code></td><td>GET</td><td>Prometheus metrics</td></tr>
|
|
59
|
+
<tr><td><code>/mcp</code></td><td>POST</td><td>MCP JSON-RPC</td></tr>
|
|
60
|
+
<tr><td><code>/sse</code></td><td>GET</td><td>Server-Sent Events</td></tr>
|
|
61
|
+
<tr><td><code>/debug</code></td><td>GET</td><td>This panel</td></tr>
|
|
62
|
+
<tr><td><code>/api/docs</code></td><td>GET</td><td>API Documentation</td></tr>
|
|
63
|
+
</table>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<h2>Tools (__TOOLS_COUNT__)</h2>
|
|
67
|
+
<div class="card">
|
|
68
|
+
<p>Use <code>tools/list</code> MCP method to get full tool list.</p>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<script>
|
|
72
|
+
setTimeout(() => location.reload(), 30000);
|
|
73
|
+
</script>
|
|
74
|
+
</body>
|
|
75
|
+
</html>
|