mn-docs-mcp 0.2.0 → 0.2.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/mcp/cli.mjs +13 -0
- package/mcp/lib.mjs +23 -1
- package/mcp/server-http.mjs +42 -8
- package/mcp/server.mjs +43 -8
- package/package.json +1 -1
package/mcp/cli.mjs
CHANGED
|
@@ -10,6 +10,7 @@ let mode = 'stdio';
|
|
|
10
10
|
let port;
|
|
11
11
|
let prebuild = false;
|
|
12
12
|
let silent = false;
|
|
13
|
+
let root;
|
|
13
14
|
|
|
14
15
|
for (let i = 0; i < args.length; i += 1) {
|
|
15
16
|
const arg = args[i];
|
|
@@ -38,10 +39,19 @@ for (let i = 0; i < args.length; i += 1) {
|
|
|
38
39
|
i += 1;
|
|
39
40
|
continue;
|
|
40
41
|
}
|
|
42
|
+
if (arg === '--root' && args[i + 1]) {
|
|
43
|
+
root = args[i + 1];
|
|
44
|
+
i += 1;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
41
47
|
if (arg?.startsWith('--port=')) {
|
|
42
48
|
port = arg.split('=')[1];
|
|
43
49
|
continue;
|
|
44
50
|
}
|
|
51
|
+
if (arg?.startsWith('--root=')) {
|
|
52
|
+
root = arg.split('=')[1];
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
45
55
|
if (arg === '--help' || arg === '-h') {
|
|
46
56
|
process.stderr.write(
|
|
47
57
|
[
|
|
@@ -51,6 +61,7 @@ for (let i = 0; i < args.length; i += 1) {
|
|
|
51
61
|
' --prebuild 启动后后台预构建索引',
|
|
52
62
|
' --verbose 输出日志(覆盖--silent)',
|
|
53
63
|
' --silent 关闭开屏与日志',
|
|
64
|
+
' --root <path> 指定文档仓库根目录',
|
|
54
65
|
' --stdio 显式使用stdio模式',
|
|
55
66
|
].join('\n') + '\n'
|
|
56
67
|
);
|
|
@@ -72,6 +83,7 @@ if (mode === 'stdio') {
|
|
|
72
83
|
process.env.MCP_SILENT = silent ? '1' : '0';
|
|
73
84
|
process.env.MN_DOCS_VERSION = version;
|
|
74
85
|
process.env.MN_DOCS_MODE = 'stdio';
|
|
86
|
+
process.env.MN_DOCS_ROOT = root || process.env.MN_DOCS_ROOT || process.cwd();
|
|
75
87
|
if (prebuild) process.env.MCP_PREBUILD = '1';
|
|
76
88
|
await import('./server.mjs');
|
|
77
89
|
} else {
|
|
@@ -79,6 +91,7 @@ if (mode === 'stdio') {
|
|
|
79
91
|
process.env.MN_DOCS_VERSION = version;
|
|
80
92
|
process.env.MN_DOCS_MODE = 'http';
|
|
81
93
|
if (port) process.env.MN_DOCS_PORT = String(port);
|
|
94
|
+
process.env.MN_DOCS_ROOT = root || process.env.MN_DOCS_ROOT || process.cwd();
|
|
82
95
|
if (prebuild) process.env.MCP_PREBUILD = '1';
|
|
83
96
|
await import('./server-http.mjs');
|
|
84
97
|
}
|
package/mcp/lib.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
+
import fsSync from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { fileURLToPath } from 'node:url';
|
|
4
5
|
import matter from 'gray-matter';
|
|
@@ -9,7 +10,28 @@ import dotenv from 'dotenv';
|
|
|
9
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
11
|
const __dirname = path.dirname(__filename);
|
|
11
12
|
|
|
12
|
-
const
|
|
13
|
+
const DEFAULT_ROOT = path.resolve(__dirname, '..');
|
|
14
|
+
|
|
15
|
+
function resolveRootDir() {
|
|
16
|
+
const envRoot = (process.env.MN_DOCS_ROOT || '').trim();
|
|
17
|
+
if (envRoot) return envRoot;
|
|
18
|
+
const cwd = process.cwd();
|
|
19
|
+
const docsFromCwd = path.join(cwd, 'src', 'content', 'docs');
|
|
20
|
+
try {
|
|
21
|
+
if (fsSyncExists(docsFromCwd)) return cwd;
|
|
22
|
+
} catch {}
|
|
23
|
+
return DEFAULT_ROOT;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function fsSyncExists(p) {
|
|
27
|
+
try {
|
|
28
|
+
return fsSync.statSync(p).isDirectory();
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const ROOT_DIR = resolveRootDir();
|
|
13
35
|
const DOCS_DIR = path.join(ROOT_DIR, 'src', 'content', 'docs');
|
|
14
36
|
const MCP_DIR = path.join(ROOT_DIR, '.mcp');
|
|
15
37
|
const INDEX_PATH = path.join(MCP_DIR, 'index.json');
|
package/mcp/server-http.mjs
CHANGED
|
@@ -12,20 +12,54 @@ function color(text, code) {
|
|
|
12
12
|
return `\x1b[${code}m${text}\x1b[0m`;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
function stripAnsi(text) {
|
|
16
|
+
return text.replace(/\x1b\[[0-9;]*m/g, '');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function stringWidth(text) {
|
|
20
|
+
const plain = stripAnsi(text);
|
|
21
|
+
let width = 0;
|
|
22
|
+
for (const char of plain) {
|
|
23
|
+
const code = char.codePointAt(0);
|
|
24
|
+
if (!code) continue;
|
|
25
|
+
const isWide =
|
|
26
|
+
(code >= 0x1100 && code <= 0x115f) ||
|
|
27
|
+
(code === 0x2329 || code === 0x232a) ||
|
|
28
|
+
(code >= 0x2e80 && code <= 0xa4cf) ||
|
|
29
|
+
(code >= 0xac00 && code <= 0xd7a3) ||
|
|
30
|
+
(code >= 0xf900 && code <= 0xfaff) ||
|
|
31
|
+
(code >= 0xfe10 && code <= 0xfe19) ||
|
|
32
|
+
(code >= 0xfe30 && code <= 0xfe6f) ||
|
|
33
|
+
(code >= 0xff00 && code <= 0xff60) ||
|
|
34
|
+
(code >= 0xffe0 && code <= 0xffe6);
|
|
35
|
+
width += isWide ? 2 : 1;
|
|
36
|
+
}
|
|
37
|
+
return width;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function padLine(line, width) {
|
|
41
|
+
const len = stringWidth(line);
|
|
42
|
+
const padding = width - len;
|
|
43
|
+
return line + (padding > 0 ? ' '.repeat(padding) : '');
|
|
44
|
+
}
|
|
45
|
+
|
|
15
46
|
function renderSplash() {
|
|
16
47
|
if (IS_SILENT) return;
|
|
17
48
|
const version = process.env.MN_DOCS_VERSION || '0.0.0';
|
|
18
49
|
const mode = process.env.MN_DOCS_MODE || 'http';
|
|
19
50
|
const port = process.env.MN_DOCS_PORT || String(PORT);
|
|
20
|
-
const
|
|
21
|
-
color(
|
|
22
|
-
color(
|
|
23
|
-
color('│ │', '38;5;45'),
|
|
24
|
-
color(`│ 模式: ${mode.padEnd(12)} 端口: ${port.padEnd(6)} 状态: 已启动 │`, '38;5;39'),
|
|
25
|
-
color('│ │', '38;5;45'),
|
|
26
|
-
color('╰──────────────────────────────────────────────────────────────╯', '38;5;45'),
|
|
51
|
+
const contentLines = [
|
|
52
|
+
color(`mn-docs-mcp v${version}`, '38;5;45'),
|
|
53
|
+
color(`模式: ${mode} 端口: ${port}`, '38;5;39'),
|
|
27
54
|
];
|
|
28
|
-
|
|
55
|
+
const maxWidth = Math.max(...contentLines.map((line) => stringWidth(line))) + 4;
|
|
56
|
+
const top = color('╭' + '─'.repeat(maxWidth) + '╮', '38;5;45');
|
|
57
|
+
const bottom = color('╰' + '─'.repeat(maxWidth) + '╯', '38;5;45');
|
|
58
|
+
const body = contentLines.map((line) => {
|
|
59
|
+
const padded = padLine(line, maxWidth - 4);
|
|
60
|
+
return color('│', '38;5;45') + ' ' + padded + ' ' + color('│', '38;5;45');
|
|
61
|
+
});
|
|
62
|
+
process.stdout.write([top, ...body, bottom].join('\n') + '\n');
|
|
29
63
|
}
|
|
30
64
|
|
|
31
65
|
async function ensureIndex() {
|
package/mcp/server.mjs
CHANGED
|
@@ -16,19 +16,54 @@ function color(text, code) {
|
|
|
16
16
|
return `\x1b[${code}m${text}\x1b[0m`;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
function stripAnsi(text) {
|
|
20
|
+
return text.replace(/\x1b\[[0-9;]*m/g, '');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function stringWidth(text) {
|
|
24
|
+
const plain = stripAnsi(text);
|
|
25
|
+
let width = 0;
|
|
26
|
+
for (const char of plain) {
|
|
27
|
+
const code = char.codePointAt(0);
|
|
28
|
+
if (!code) continue;
|
|
29
|
+
// CJK / Fullwidth / Wide characters
|
|
30
|
+
const isWide =
|
|
31
|
+
(code >= 0x1100 && code <= 0x115f) ||
|
|
32
|
+
(code === 0x2329 || code === 0x232a) ||
|
|
33
|
+
(code >= 0x2e80 && code <= 0xa4cf) ||
|
|
34
|
+
(code >= 0xac00 && code <= 0xd7a3) ||
|
|
35
|
+
(code >= 0xf900 && code <= 0xfaff) ||
|
|
36
|
+
(code >= 0xfe10 && code <= 0xfe19) ||
|
|
37
|
+
(code >= 0xfe30 && code <= 0xfe6f) ||
|
|
38
|
+
(code >= 0xff00 && code <= 0xff60) ||
|
|
39
|
+
(code >= 0xffe0 && code <= 0xffe6);
|
|
40
|
+
width += isWide ? 2 : 1;
|
|
41
|
+
}
|
|
42
|
+
return width;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function padLine(line, width) {
|
|
46
|
+
const len = stringWidth(line);
|
|
47
|
+
const padding = width - len;
|
|
48
|
+
return line + (padding > 0 ? ' '.repeat(padding) : '');
|
|
49
|
+
}
|
|
50
|
+
|
|
19
51
|
function renderSplash() {
|
|
20
52
|
if (IS_SILENT) return;
|
|
21
53
|
const version = process.env.MN_DOCS_VERSION || '0.0.0';
|
|
22
54
|
const mode = process.env.MN_DOCS_MODE || 'stdio';
|
|
23
|
-
const
|
|
24
|
-
color(
|
|
25
|
-
color(
|
|
26
|
-
color('│ │', '38;5;45'),
|
|
27
|
-
color(`│ 模式: ${mode.padEnd(12)} 状态: 已启动 │`, '38;5;39'),
|
|
28
|
-
color('│ │', '38;5;45'),
|
|
29
|
-
color('╰──────────────────────────────────────────────────────────────╯', '38;5;45'),
|
|
55
|
+
const contentLines = [
|
|
56
|
+
color(`mn-docs-mcp v${version}`, '38;5;45'),
|
|
57
|
+
color(`模式: ${mode}`, '38;5;39'),
|
|
30
58
|
];
|
|
31
|
-
|
|
59
|
+
const maxWidth = Math.max(...contentLines.map((line) => stringWidth(line))) + 4;
|
|
60
|
+
const top = color('╭' + '─'.repeat(maxWidth) + '╮', '38;5;45');
|
|
61
|
+
const bottom = color('╰' + '─'.repeat(maxWidth) + '╯', '38;5;45');
|
|
62
|
+
const body = contentLines.map((line) => {
|
|
63
|
+
const padded = padLine(line, maxWidth - 4);
|
|
64
|
+
return color('│', '38;5;45') + ' ' + padded + ' ' + color('│', '38;5;45');
|
|
65
|
+
});
|
|
66
|
+
process.stderr.write([top, ...body, bottom].join('\n') + '\n');
|
|
32
67
|
}
|
|
33
68
|
|
|
34
69
|
async function ensureIndex() {
|