basedoc-dameng-mcp 0.1.1 → 0.1.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/README.md +161 -88
- package/dist/bin-fetch.js +92 -0
- package/dist/bin-fetch.js.map +1 -0
- package/dist/fetch-docs.js +175 -0
- package/dist/fetch-docs.js.map +1 -0
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -1,128 +1,201 @@
|
|
|
1
|
-
# dameng-mcp
|
|
1
|
+
# basedoc-dameng-mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
面向达梦数据库 (DM8) 的只读 MCP 服务,附带**本地 Markdown 手册**的离线全文检索能力。把"运维问答"和"翻手册"这两件事做成 LLM 直接可调用的工具,让 Claude / 其它 MCP 客户端在一次会话里既能问数据库当前状态,也能查官方文档。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
整套搜索能力**完全离线**——内网部署的达梦客户也能用。
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- **No password fallback.** `DAMENG_PASSWORD` must be set; missing → exit 2. Nothing defaults to `SYSDBA`.
|
|
9
|
-
- **stderr-only logging.** stdio's stdout is the JSON-RPC channel — every log line goes through `console.error`.
|
|
10
|
-
- **Timeout + row cap.** Every query is bounded by `DAMENG_QUERY_TIMEOUT_MS` (default 10s) and capped at `DAMENG_MAX_ROWS` (default 1000). On timeout the underlying connection is closed.
|
|
11
|
-
- **Pinned dependencies.** `dmdb`, `@modelcontextprotocol/sdk`, and `zod` are all locked to exact versions.
|
|
7
|
+
---
|
|
12
8
|
|
|
13
|
-
|
|
9
|
+
## 快速开始
|
|
14
10
|
|
|
15
|
-
|
|
11
|
+
接入这个 MCP 需要先准备一个 markdown 手册目录(用作搜索语料)。两条路:
|
|
16
12
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
13
|
+
### 路径 A:你已经有 markdown 手册目录
|
|
14
|
+
|
|
15
|
+
直接跳到下面的 [接入 Claude Code](#接入-claude-code),把 `DAMENG_DOCS_ROOT` 指过去。
|
|
16
|
+
|
|
17
|
+
### 路径 B:你还没有,从达梦官方文档站抓一份
|
|
18
|
+
|
|
19
|
+
包内置了一个抓取转换工具(`basedoc-dameng-fetch-docs`),从 https://eco.dameng.com 拉所有手册并自动转 markdown,写到本地目录:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx -y basedoc-dameng-mcp basedoc-dameng-fetch-docs ~/.dameng-docs
|
|
23
|
+
# 或者直接调 bin:
|
|
24
|
+
npx -y -p basedoc-dameng-mcp basedoc-dameng-fetch-docs ~/.dameng-docs
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
完成后会得到 22 本手册、~250 个章节、~9 MB 的 markdown 目录,下面像这样:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
~/.dameng-docs/
|
|
31
|
+
├── README.md
|
|
32
|
+
├── DM8-系统管理员手册/
|
|
33
|
+
│ ├── system-administrator.md
|
|
34
|
+
│ ├── physical-storage-structure.md
|
|
35
|
+
│ └── ...
|
|
36
|
+
├── DM8-SQL 语言使用手册/
|
|
37
|
+
├── DM8-安全管理/
|
|
38
|
+
└── ...
|
|
25
39
|
```
|
|
26
40
|
|
|
27
|
-
|
|
41
|
+
抓取过程默认 5 并发、约 2-3 秒结束。需要联通 `eco.dameng.com`——内网/隔离环境跑不了,请走路径 A 或者在能联网的机器上抓完后再 rsync 进内网。
|
|
42
|
+
|
|
43
|
+
> **关于版权**:抓取下来的内容版权属于**达梦数据库股份有限公司**,仅供个人/团队内部参考使用,**不要公开发布**。
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 接入 Claude Code
|
|
28
48
|
|
|
29
49
|
```bash
|
|
30
|
-
|
|
31
|
-
|
|
50
|
+
claude mcp add dameng \
|
|
51
|
+
--scope user \
|
|
52
|
+
-e DAMENG_DOCS_ROOT="$HOME/.dameng-docs" \
|
|
53
|
+
-e DAMENG_DOCS_INCLUDE='DM8-*' \
|
|
54
|
+
-e DAMENG_HOST=<达梦主机> \
|
|
55
|
+
-e DAMENG_PORT=5236 \
|
|
56
|
+
-e DAMENG_USER=MCP_RO \
|
|
57
|
+
-e DAMENG_PASSWORD=<密码> \
|
|
58
|
+
-- npx -y basedoc-dameng-mcp
|
|
32
59
|
```
|
|
33
60
|
|
|
34
|
-
|
|
61
|
+
不需要数据库工具的话,所有 `DAMENG_HOST` / `PORT` / `USER` / `PASSWORD` 都可以省略——只剩文档检索能力(**仅文档模式**)。
|
|
35
62
|
|
|
36
|
-
|
|
63
|
+
---
|
|
37
64
|
|
|
38
|
-
|
|
39
|
-
- **Docs-only mode** (set only `DAMENG_DOCS_ROOT`, no DB credentials): just the 4 docs tools — useful when you want to ship Dameng manuals to an LLM without exposing the database.
|
|
40
|
-
- (DB-only mode also works if you set DB creds without `DAMENG_DOCS_ROOT`.)
|
|
65
|
+
## 启动模式
|
|
41
66
|
|
|
42
|
-
|
|
67
|
+
按环境变量自动判定:
|
|
43
68
|
|
|
44
|
-
|
|
69
|
+
| 给的环境变量 | 启动什么 |
|
|
70
|
+
| --- | --- |
|
|
71
|
+
| 完整 DB 凭据 + `DAMENG_DOCS_ROOT` | 全部 8 个工具 |
|
|
72
|
+
| 只有 DB 凭据 | 只 4 个 DB 工具 |
|
|
73
|
+
| 只有 `DAMENG_DOCS_ROOT` | 只 4 个文档工具——适合"只让 LLM 看手册、不暴露数据库" |
|
|
74
|
+
| 都没有 | exit 2 |
|
|
45
75
|
|
|
46
|
-
|
|
47
|
-
|---|---|---|---|
|
|
48
|
-
| `DAMENG_HOST` | for DB | — | e.g. `dameng.example.com` |
|
|
49
|
-
| `DAMENG_PORT` | for DB | — | e.g. `5236` |
|
|
50
|
-
| `DAMENG_USER` | for DB | — | low-privilege account |
|
|
51
|
-
| `DAMENG_PASSWORD` | for DB | — | no fallback |
|
|
52
|
-
| `DAMENG_SCHEMA` | no | — | default schema for `list_tables` / `describe_table` |
|
|
53
|
-
| `DAMENG_QUERY_TIMEOUT_MS` | no | `10000` | per-query timeout |
|
|
54
|
-
| `DAMENG_MAX_ROWS` | no | `1000` | hard cap on returned rows |
|
|
55
|
-
| `DAMENG_POOL_MIN` | no | `1` | |
|
|
56
|
-
| `DAMENG_POOL_MAX` | no | `4` | |
|
|
57
|
-
| `DAMENG_DOCS_ROOT` | no | — | absolute path to a directory of Markdown manuals. Enables the docs tools. |
|
|
58
|
-
| `DAMENG_DOCS_INCLUDE` | no | — | comma-separated top-level dir patterns to include (glob `*` supported). Recommended: `DM8-*` to scope to official manuals only. |
|
|
59
|
-
| `DAMENG_DOCS_EXCLUDE` | no | — | comma-separated top-level dir patterns to exclude. `node_modules` is always excluded. |
|
|
60
|
-
| `DAMENG_NO_LEGACY_OPENSSL` | no | unset | Set to `1` to skip the auto-relaunch with `--openssl-legacy-provider` (only useful if your dmdb build doesn't need it). |
|
|
76
|
+
---
|
|
61
77
|
|
|
62
|
-
##
|
|
78
|
+
## 工具
|
|
63
79
|
|
|
64
|
-
###
|
|
65
|
-
- `query(sql)` — run a single read-only statement.
|
|
66
|
-
- `list_tables(schema?)` — tables in a schema (defaults to `DAMENG_SCHEMA` or current user).
|
|
67
|
-
- `describe_table(table, schema?)` — columns + types for one table.
|
|
68
|
-
- `instance_status()` — `V$INSTANCE` + `V$DATABASE` snapshot.
|
|
80
|
+
### 数据库(需要 DB 凭据)
|
|
69
81
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
82
|
+
| 工具 | 说明 |
|
|
83
|
+
| --- | --- |
|
|
84
|
+
| `query(sql)` | 执行单条只读 SQL |
|
|
85
|
+
| `list_tables(schema?)` | 列某 schema 下的表 |
|
|
86
|
+
| `describe_table(table, schema?)` | 列字段名 / 类型 / 是否可空 / 默认值 |
|
|
87
|
+
| `instance_status()` | `V$INSTANCE` + `V$DATABASE` 当前快照 |
|
|
75
88
|
|
|
76
|
-
|
|
77
|
-
1. `list_manuals()` → know what's available
|
|
78
|
-
2. `lookup_docs("keyword", manual: "DM8-...")` → find candidate sections
|
|
79
|
-
3. `read_section(file, heading)` → pull the full passage
|
|
80
|
-
4. Synthesize the answer for the user
|
|
89
|
+
### 文档(需要 `DAMENG_DOCS_ROOT`)
|
|
81
90
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
91
|
+
| 工具 | 说明 |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| `list_manuals()` | 根目录下都有哪些手册(用 `00-目录索引.md` / `README.md` 当摘要) |
|
|
94
|
+
| `list_sections(manual)` | 某本手册里有哪些 .md 章节,每个带 H1 / H2 标题 + 行号 |
|
|
95
|
+
| `read_section(file, heading?, maxBytes?)` | 读整个章节文件;带 heading 则只切出对应小节 |
|
|
96
|
+
| `lookup_docs(query, manual?, regex?, maxMatches?)` | 全文检索;按 (文件 + 最近 heading) 分组,每条带 ±1 行上下文,按命中数倒序 |
|
|
85
97
|
|
|
86
|
-
|
|
98
|
+
### 给 LLM 客户端的推荐工作流
|
|
87
99
|
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
-e DAMENG_DOCS_INCLUDE='DM8-*' \
|
|
97
|
-
-- npx -y basedoc-dameng-mcp
|
|
100
|
+
```
|
|
101
|
+
list_manuals()
|
|
102
|
+
↓
|
|
103
|
+
lookup_docs(query, manual="DM8-...")
|
|
104
|
+
↓
|
|
105
|
+
read_section(file, heading="...")
|
|
106
|
+
↓
|
|
107
|
+
(合成回答)
|
|
98
108
|
```
|
|
99
109
|
|
|
100
|
-
|
|
110
|
+
`list_tables` 和 `describe_table` 内部生成 SQL,对入参做严格的标识符正则校验(`[A-Za-z_][A-Za-z0-9_]*`),不会拼接出注入。
|
|
101
111
|
|
|
102
|
-
|
|
103
|
-
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## 取舍清单
|
|
115
|
+
|
|
116
|
+
不该让 LLM 做的事,挡在工具调用之前:
|
|
117
|
+
|
|
118
|
+
- SQL 入参只放 `SELECT` / `WITH` / `EXPLAIN`,多语句 / 注释绕过 / `DROP` 之类的关键字一律拒绝
|
|
119
|
+
- `DAMENG_PASSWORD` 不写也不补——缺失就直接退出 (exit 2),不存在 SYSDBA 兜底
|
|
120
|
+
- stdout 留给 JSON-RPC,所有日志都走 stderr
|
|
121
|
+
- 每个查询带超时 + 行数硬上限
|
|
122
|
+
- 文档相关的工具不能跨出 `DAMENG_DOCS_ROOT` 边界——`..`、绝对路径、`node_modules` 全部拦截
|
|
123
|
+
- 关键依赖(`@modelcontextprotocol/sdk`、`dmdb`、`zod`、`turndown`)锁死精确版本
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 环境变量
|
|
128
|
+
|
|
129
|
+
| 变量 | 何时必填 | 默认 | 说明 |
|
|
130
|
+
| --- | --- | --- | --- |
|
|
131
|
+
| `DAMENG_HOST` | DB 模式 | — | 达梦服务器地址 |
|
|
132
|
+
| `DAMENG_PORT` | DB 模式 | — | 比如 `5236` |
|
|
133
|
+
| `DAMENG_USER` | DB 模式 | — | 推荐建低权账号 |
|
|
134
|
+
| `DAMENG_PASSWORD` | DB 模式 | — | 没有任何兜底 |
|
|
135
|
+
| `DAMENG_SCHEMA` | 否 | — | `list_tables` / `describe_table` 默认查的 schema |
|
|
136
|
+
| `DAMENG_QUERY_TIMEOUT_MS` | 否 | `10000` | 单次查询超时 |
|
|
137
|
+
| `DAMENG_MAX_ROWS` | 否 | `1000` | 单次返回行数硬上限 |
|
|
138
|
+
| `DAMENG_POOL_MIN` / `DAMENG_POOL_MAX` | 否 | `1` / `4` | 连接池上下限 |
|
|
139
|
+
| `DAMENG_DOCS_ROOT` | 文档模式 | — | 含 markdown 的目录的**绝对路径** |
|
|
140
|
+
| `DAMENG_DOCS_INCLUDE` | 否 | — | 顶层目录白名单,逗号分隔,支持 `*` 通配,例如 `DM8-*` |
|
|
141
|
+
| `DAMENG_DOCS_EXCLUDE` | 否 | — | 顶层目录黑名单。`node_modules` 永远硬排,无需手动加 |
|
|
142
|
+
| `DAMENG_NO_LEGACY_OPENSSL` | 否 | 不设 | 设 `1` 跳过下文那个自重启 |
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 准备一个低权数据库账号
|
|
147
|
+
|
|
148
|
+
直接给 SYSDBA 跑也能用——SQL 守卫已经限死只读——但纵深防御原则下建议:
|
|
149
|
+
|
|
150
|
+
```sql
|
|
151
|
+
CREATE USER MCP_RO IDENTIFIED BY "<强密码>";
|
|
152
|
+
GRANT SELECT ANY TABLE TO MCP_RO;
|
|
153
|
+
GRANT SELECT ON V$INSTANCE TO MCP_RO;
|
|
154
|
+
GRANT SELECT ON V$DATABASE TO MCP_RO;
|
|
104
155
|
```
|
|
105
156
|
|
|
106
|
-
|
|
157
|
+
注:达梦中 `CREATE USER` 后默认就有 session 权限,无需 `GRANT CONNECT`(这条会报语法错)。
|
|
107
158
|
|
|
108
|
-
|
|
159
|
+
---
|
|
109
160
|
|
|
110
|
-
|
|
161
|
+
## 二进制为什么会自重启
|
|
162
|
+
|
|
163
|
+
`dmdb` 驱动握手阶段使用了 OpenSSL 3 在 Node 17+ 默认禁用的算法。不带 `--openssl-legacy-provider` 启动 Node 时会报 `[6071] 消息加密失败` / `error:0308010C`。本包的 bin 入口检测到没带这个 flag 就自己 spawn 一次新进程加上,对调用者透明。代价是约 50ms 的额外启动。如果你的 dmdb 版本不需要,设 `DAMENG_NO_LEGACY_OPENSSL=1` 跳过。
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## 测试
|
|
168
|
+
|
|
169
|
+
```
|
|
111
170
|
npm test
|
|
112
171
|
```
|
|
113
172
|
|
|
114
|
-
|
|
173
|
+
117 个用例,覆盖:
|
|
174
|
+
|
|
175
|
+
- SQL 守卫的关键字白名单 / 注释剥离 / 多语句检测
|
|
176
|
+
- 配置加载,包括无 SYSDBA 兜底的明确断言、include/exclude 解析
|
|
177
|
+
- 超时 helper
|
|
178
|
+
- 4 个文档函数:搜索分组与排名、目录列出、章节按 heading 切片读取、路径越权防护
|
|
179
|
+
- HTML→MD 抓取链路:sidebar 解析、article 抽取、turndown 包装
|
|
180
|
+
|
|
181
|
+
dmdb 与 MCP 协议握手部分没有单元测试,需要指向真实达梦实例验证。
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 故意不做的能力
|
|
186
|
+
|
|
187
|
+
- 不暴露 DML / DDL / 系统过程调用——写入操作请直接走 dmctl 或 JDBC
|
|
188
|
+
- 不开 HTTP transport——只 stdio,没有可被外部访问的网络面
|
|
189
|
+
- 启动时不做 schema 自检——按需查系统视图,避免不必要的握手成本
|
|
115
190
|
|
|
116
|
-
|
|
191
|
+
---
|
|
117
192
|
|
|
118
|
-
|
|
119
|
-
- No HTTP transport. stdio only — no inbound network surface.
|
|
120
|
-
- No automatic schema introspection on startup. Tools query system views on demand.
|
|
193
|
+
## 已知传递依赖告警
|
|
121
194
|
|
|
122
|
-
|
|
195
|
+
`npm audit` 会标记 `@modelcontextprotocol/sdk` HTTP transport 链(`express-rate-limit` → `ip-address`)以及开发环境 Vitest / Vite 链上的几个中等漏洞。本服务只跑 stdio transport,HTTP 那条链不会被加载;Vitest 是开发依赖不进入运行时。等上游升级后会自动消失,不会用 `npm audit fix --force` 强行覆盖。
|
|
123
196
|
|
|
124
|
-
|
|
197
|
+
---
|
|
125
198
|
|
|
126
|
-
##
|
|
199
|
+
## 许可证
|
|
127
200
|
|
|
128
|
-
|
|
201
|
+
MIT。详见 [LICENSE](./LICENSE)。
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { fetchAndConvertAll } from "./fetch-docs.js";
|
|
4
|
+
const DEFAULT_SOURCE = "https://eco.dameng.com/document/dm/zh-cn/pm/";
|
|
5
|
+
function usage() {
|
|
6
|
+
process.stderr.write([
|
|
7
|
+
"用法: basedoc-dameng-fetch-docs <目标目录> [--source <URL>] [--concurrency <N>]",
|
|
8
|
+
"",
|
|
9
|
+
" 从达梦官方在线文档抓取所有手册并转换为 Markdown,写入指定目录。",
|
|
10
|
+
" 目标目录可以直接当作 DAMENG_DOCS_ROOT 给 basedoc-dameng-mcp 使用。",
|
|
11
|
+
"",
|
|
12
|
+
"选项:",
|
|
13
|
+
" --source <URL> 达梦文档站点的索引页 URL",
|
|
14
|
+
` 默认: ${DEFAULT_SOURCE}`,
|
|
15
|
+
" --concurrency <N> 并发下载数 (默认 5)",
|
|
16
|
+
" -h, --help 显示帮助",
|
|
17
|
+
"",
|
|
18
|
+
"示例:",
|
|
19
|
+
" basedoc-dameng-fetch-docs ~/.dameng-docs",
|
|
20
|
+
" npx -y basedoc-dameng-mcp/dist/bin-fetch.js ~/.dameng-docs",
|
|
21
|
+
"",
|
|
22
|
+
"注意: 抓取到的内容版权属于达梦数据库股份有限公司。仅供个人或团队",
|
|
23
|
+
" 内部使用,不要公开再发布。",
|
|
24
|
+
"",
|
|
25
|
+
].join("\n"));
|
|
26
|
+
process.exit(2);
|
|
27
|
+
}
|
|
28
|
+
function parseArgs(argv) {
|
|
29
|
+
let target;
|
|
30
|
+
let source = DEFAULT_SOURCE;
|
|
31
|
+
let concurrency = 5;
|
|
32
|
+
for (let i = 0; i < argv.length; i++) {
|
|
33
|
+
const a = argv[i];
|
|
34
|
+
if (a === "-h" || a === "--help")
|
|
35
|
+
usage();
|
|
36
|
+
else if (a === "--source")
|
|
37
|
+
source = argv[++i] ?? usage();
|
|
38
|
+
else if (a === "--concurrency")
|
|
39
|
+
concurrency = parseInt(argv[++i] ?? "", 10) || usage();
|
|
40
|
+
else if (a.startsWith("-")) {
|
|
41
|
+
process.stderr.write(`未知选项: ${a}\n`);
|
|
42
|
+
usage();
|
|
43
|
+
}
|
|
44
|
+
else if (!target)
|
|
45
|
+
target = a;
|
|
46
|
+
else {
|
|
47
|
+
process.stderr.write(`只支持一个目标目录参数,已经是 ${target}\n`);
|
|
48
|
+
usage();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!target)
|
|
52
|
+
usage();
|
|
53
|
+
if (concurrency < 1 || concurrency > 20) {
|
|
54
|
+
process.stderr.write(`concurrency 取值必须在 1–20 之间,得到 ${concurrency}\n`);
|
|
55
|
+
process.exit(2);
|
|
56
|
+
}
|
|
57
|
+
return { target: resolve(target), source, concurrency };
|
|
58
|
+
}
|
|
59
|
+
async function main() {
|
|
60
|
+
const args = parseArgs(process.argv.slice(2));
|
|
61
|
+
process.stderr.write(`目标目录: ${args.target}\n`);
|
|
62
|
+
process.stderr.write(`源站点: ${args.source}\n`);
|
|
63
|
+
process.stderr.write(`并发: ${args.concurrency}\n\n`);
|
|
64
|
+
const t0 = Date.now();
|
|
65
|
+
const result = await fetchAndConvertAll({
|
|
66
|
+
source: args.source,
|
|
67
|
+
targetDir: args.target,
|
|
68
|
+
concurrency: args.concurrency,
|
|
69
|
+
onProgress: (m) => process.stderr.write(m + "\n"),
|
|
70
|
+
});
|
|
71
|
+
const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
|
|
72
|
+
process.stderr.write(`\n完成: ${result.manualCount} 本手册, ${result.chapterCount} 个章节, ${elapsed}s\n`);
|
|
73
|
+
if (result.failures.length > 0) {
|
|
74
|
+
process.stderr.write(`\n失败 ${result.failures.length} 个章节:\n`);
|
|
75
|
+
for (const f of result.failures) {
|
|
76
|
+
process.stderr.write(` ${f.url}: ${f.error}\n`);
|
|
77
|
+
}
|
|
78
|
+
process.stderr.write(`\n可以重新运行同一条命令来重试 (已成功的章节会被覆盖)。\n`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
process.stderr.write(`\n下一步: 把 ${args.target} 当 DAMENG_DOCS_ROOT 用,例如:\n` +
|
|
82
|
+
` claude mcp add dameng \\\n` +
|
|
83
|
+
` --scope user \\\n` +
|
|
84
|
+
` -e DAMENG_DOCS_ROOT="${args.target}" \\\n` +
|
|
85
|
+
` -e DAMENG_DOCS_INCLUDE='DM8-*' \\\n` +
|
|
86
|
+
` -- npx -y basedoc-dameng-mcp\n`);
|
|
87
|
+
}
|
|
88
|
+
main().catch((e) => {
|
|
89
|
+
process.stderr.write(`[fetch-docs] 错误: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
});
|
|
92
|
+
//# sourceMappingURL=bin-fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin-fetch.js","sourceRoot":"","sources":["../src/bin-fetch.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,MAAM,cAAc,GAAG,8CAA8C,CAAC;AAEtE,SAAS,KAAK;IACZ,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB;QACE,2EAA2E;QAC3E,EAAE;QACF,wCAAwC;QACxC,wDAAwD;QACxD,EAAE;QACF,KAAK;QACL,sCAAsC;QACtC,6BAA6B,cAAc,EAAE;QAC7C,oCAAoC;QACpC,4BAA4B;QAC5B,EAAE;QACF,KAAK;QACL,4CAA4C;QAC5C,8DAA8D;QAC9D,EAAE;QACF,mCAAmC;QACnC,qBAAqB;QACrB,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAQD,SAAS,SAAS,CAAC,IAAc;IAC/B,IAAI,MAA0B,CAAC;IAC/B,IAAI,MAAM,GAAG,cAAc,CAAC;IAC5B,IAAI,WAAW,GAAG,CAAC,CAAC;IAEpB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;QACnB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,QAAQ;YAAE,KAAK,EAAE,CAAC;aACrC,IAAI,CAAC,KAAK,UAAU;YAAE,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC;aACpD,IAAI,CAAC,KAAK,eAAe;YAAE,WAAW,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC;aAClF,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YACrC,KAAK,EAAE,CAAC;QACV,CAAC;aAAM,IAAI,CAAC,MAAM;YAAE,MAAM,GAAG,CAAC,CAAC;aAC1B,CAAC;YACJ,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,mBAAmB,MAAM,IAAI,CAAC,CAAC;YACpD,KAAK,EAAE,CAAC;QACV,CAAC;IACH,CAAC;IACD,IAAI,CAAC,MAAM;QAAE,KAAK,EAAE,CAAC;IACrB,IAAI,WAAW,GAAG,CAAC,IAAI,WAAW,GAAG,EAAE,EAAE,CAAC;QACxC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gCAAgC,WAAW,IAAI,CAAC,CAAC;QACtE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,MAAO,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;AAC3D,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;IAC/C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;IAC9C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,WAAW,MAAM,CAAC,CAAC;IAEpD,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACtB,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC;QACtC,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,SAAS,EAAE,IAAI,CAAC,MAAM;QACtB,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC;KAClD,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAEtD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,SAAS,MAAM,CAAC,WAAW,SAAS,MAAM,CAAC,YAAY,SAAS,OAAO,KAAK,CAC7E,CAAC;IACF,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,MAAM,CAAC,QAAQ,CAAC,MAAM,SAAS,CAAC,CAAC;QAC9D,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YAChC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;QACnD,CAAC;QACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,kCAAkC,CACnC,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,YAAY,IAAI,CAAC,MAAM,6BAA6B;QAClD,8BAA8B;QAC9B,uBAAuB;QACvB,4BAA4B,IAAI,CAAC,MAAM,QAAQ;QAC/C,yCAAyC;QACzC,oCAAoC,CACvC,CAAC;AACJ,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;IACjB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACzF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import TurndownService from "turndown";
|
|
4
|
+
const ICONFONT_RANGE = /[-]/g;
|
|
5
|
+
const HTML_ICONFONT_ENTITY = /&#x[eE][0-9a-fA-F]{3};/g;
|
|
6
|
+
export function normalizeManualName(raw) {
|
|
7
|
+
return raw
|
|
8
|
+
.replace(HTML_ICONFONT_ENTITY, "")
|
|
9
|
+
.replace(ICONFONT_RANGE, "")
|
|
10
|
+
.replace(/\s+/g, " ")
|
|
11
|
+
.trim()
|
|
12
|
+
.replace(/^DM8\s+/, "DM8-");
|
|
13
|
+
}
|
|
14
|
+
export function parseManualsFromSidebar(html) {
|
|
15
|
+
// Each manual is rendered as `<div class="left-sidebar-link-group top" ...>`
|
|
16
|
+
// The literal string `left-sidebar-link-group` also appears inside <script>
|
|
17
|
+
// blocks for the toggle animation; we anchor to the `top` modifier so we
|
|
18
|
+
// only match the actual HTML wrappers.
|
|
19
|
+
const blockRe = /<div\s+class="left-sidebar-link-group top"[^>]*>/g;
|
|
20
|
+
const positions = [];
|
|
21
|
+
let mm;
|
|
22
|
+
while ((mm = blockRe.exec(html)) !== null)
|
|
23
|
+
positions.push(mm.index);
|
|
24
|
+
const out = [];
|
|
25
|
+
for (let i = 0; i < positions.length; i++) {
|
|
26
|
+
const start = positions[i];
|
|
27
|
+
const end = i + 1 < positions.length ? positions[i + 1] : html.length;
|
|
28
|
+
let block = html.slice(start, end);
|
|
29
|
+
// Bound to the closing of this group's container — the `bar-border`
|
|
30
|
+
// separator that always follows a finished group.
|
|
31
|
+
const closeAt = block.search(/<div\s+class="bar-border"/);
|
|
32
|
+
if (closeAt > 0)
|
|
33
|
+
block = block.slice(0, closeAt);
|
|
34
|
+
// Manual name is the first font-weight:800 anchor in the block.
|
|
35
|
+
const titleMatch = block.match(/<a[^>]*font-weight:\s*800;?[^>]*>([\s\S]*?)<\/a>/);
|
|
36
|
+
if (!titleMatch)
|
|
37
|
+
continue;
|
|
38
|
+
const rawTitle = titleMatch[1].replace(/<[^>]+>/g, "");
|
|
39
|
+
const manual = normalizeManualName(rawTitle);
|
|
40
|
+
if (!manual.startsWith("DM8-"))
|
|
41
|
+
continue;
|
|
42
|
+
const chapters = [];
|
|
43
|
+
const chapterRe = /<a[^>]*href="([^"]+\.html)"[^>]*>([\s\S]*?)<\/a>/g;
|
|
44
|
+
let m;
|
|
45
|
+
while ((m = chapterRe.exec(block)) !== null) {
|
|
46
|
+
const href = m[1];
|
|
47
|
+
if (!href.endsWith(".html") || href.includes("/") || href.includes(":"))
|
|
48
|
+
continue;
|
|
49
|
+
const title = m[2].replace(/<[^>]+>/g, "").replace(ICONFONT_RANGE, "").trim();
|
|
50
|
+
if (!title)
|
|
51
|
+
continue;
|
|
52
|
+
chapters.push({ href, title });
|
|
53
|
+
}
|
|
54
|
+
if (chapters.length > 0)
|
|
55
|
+
out.push({ manual, chapters });
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
export function extractArticleHtml(html) {
|
|
60
|
+
// The eco.dameng.com pages wrap the actual chapter content in
|
|
61
|
+
// <div class="article-content vditor-reset" ...> ... </div>
|
|
62
|
+
// followed by a <footer class="article-footer"> we want to drop.
|
|
63
|
+
const m = html.match(/<div\s+class="article-content[^"]*"[^>]*>([\s\S]*?)<(?:footer|div|aside)[^>]*\sclass="article-footer/);
|
|
64
|
+
return m ? m[1].trim() : "";
|
|
65
|
+
}
|
|
66
|
+
let _turndown = null;
|
|
67
|
+
function turndown() {
|
|
68
|
+
if (_turndown)
|
|
69
|
+
return _turndown;
|
|
70
|
+
const t = new TurndownService({
|
|
71
|
+
headingStyle: "atx",
|
|
72
|
+
codeBlockStyle: "fenced",
|
|
73
|
+
bulletListMarker: "-",
|
|
74
|
+
emDelimiter: "*",
|
|
75
|
+
});
|
|
76
|
+
// Drop the inline anchor links Hexo adds after every heading.
|
|
77
|
+
t.addRule("strip-article-anchor", {
|
|
78
|
+
filter: (node) => node.nodeName === "A" &&
|
|
79
|
+
(node.getAttribute("class") || "").includes("article-anchor"),
|
|
80
|
+
replacement: () => "",
|
|
81
|
+
});
|
|
82
|
+
// Drop "copy code" buttons commonly added by docs sites.
|
|
83
|
+
t.addRule("strip-copy-button", {
|
|
84
|
+
filter: (node) => node.nodeName === "BUTTON" ||
|
|
85
|
+
(node.getAttribute("class") || "").includes("copy"),
|
|
86
|
+
replacement: () => "",
|
|
87
|
+
});
|
|
88
|
+
_turndown = t;
|
|
89
|
+
return t;
|
|
90
|
+
}
|
|
91
|
+
export function htmlToMarkdown(html) {
|
|
92
|
+
return turndown().turndown(html).trim() + "\n";
|
|
93
|
+
}
|
|
94
|
+
export async function fetchAndConvertAll(opts) {
|
|
95
|
+
const concurrency = opts.concurrency ?? 5;
|
|
96
|
+
const log = opts.onProgress ?? (() => { });
|
|
97
|
+
const baseUrl = opts.source.endsWith("/") ? opts.source : opts.source + "/";
|
|
98
|
+
log(`fetching index: ${baseUrl}`);
|
|
99
|
+
const indexHtml = await fetchText(baseUrl);
|
|
100
|
+
const manuals = parseManualsFromSidebar(indexHtml);
|
|
101
|
+
if (manuals.length === 0) {
|
|
102
|
+
throw new Error(`No manuals found in sidebar at ${baseUrl}. Did the site structure change?`);
|
|
103
|
+
}
|
|
104
|
+
log(`found ${manuals.length} manuals, ${manuals.reduce((n, m) => n + m.chapters.length, 0)} chapters total`);
|
|
105
|
+
await mkdir(opts.targetDir, { recursive: true });
|
|
106
|
+
await writeFile(join(opts.targetDir, "README.md"), `# Dameng manuals (auto-fetched)\n\nGenerated from ${baseUrl} at ${new Date().toISOString()}.\n\nCopyright belongs to 达梦数据库股份有限公司. Kept locally for personal/team reference.\n`, "utf8");
|
|
107
|
+
const tasks = [];
|
|
108
|
+
for (const group of manuals) {
|
|
109
|
+
const dir = join(opts.targetDir, group.manual);
|
|
110
|
+
await mkdir(dir, { recursive: true });
|
|
111
|
+
for (const ch of group.chapters) {
|
|
112
|
+
tasks.push({
|
|
113
|
+
manual: group.manual,
|
|
114
|
+
chapter: ch,
|
|
115
|
+
url: baseUrl + ch.href,
|
|
116
|
+
outPath: join(dir, ch.href.replace(/\.html$/, ".md")),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const failures = [];
|
|
121
|
+
let completed = 0;
|
|
122
|
+
const total = tasks.length;
|
|
123
|
+
async function worker(slice) {
|
|
124
|
+
for (const t of slice) {
|
|
125
|
+
try {
|
|
126
|
+
const html = await fetchText(t.url);
|
|
127
|
+
const article = extractArticleHtml(html);
|
|
128
|
+
if (!article)
|
|
129
|
+
throw new Error("no article body in HTML");
|
|
130
|
+
const md = `# ${t.chapter.title}\n\n` + htmlToMarkdown(article);
|
|
131
|
+
await writeFile(t.outPath, md, "utf8");
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
failures.push({ url: t.url, error: e instanceof Error ? e.message : String(e) });
|
|
135
|
+
}
|
|
136
|
+
completed++;
|
|
137
|
+
if (completed % 10 === 0 || completed === total) {
|
|
138
|
+
log(` [${completed}/${total}] ${t.manual} :: ${t.chapter.title}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Slice tasks evenly across workers.
|
|
143
|
+
const slices = Array.from({ length: concurrency }, () => []);
|
|
144
|
+
tasks.forEach((t, i) => slices[i % concurrency].push(t));
|
|
145
|
+
await Promise.all(slices.map((s) => worker(s)));
|
|
146
|
+
return {
|
|
147
|
+
manualCount: manuals.length,
|
|
148
|
+
chapterCount: tasks.length,
|
|
149
|
+
failures,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
async function fetchText(url, retries = 2) {
|
|
153
|
+
let lastErr;
|
|
154
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
155
|
+
try {
|
|
156
|
+
const res = await fetch(url, {
|
|
157
|
+
headers: {
|
|
158
|
+
"User-Agent": "basedoc-dameng-mcp/0.1.2 (+https://github.com/bravejack/basedoc-dameng-mcp)",
|
|
159
|
+
"Accept": "text/html",
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
if (!res.ok)
|
|
163
|
+
throw new Error(`HTTP ${res.status} for ${url}`);
|
|
164
|
+
return await res.text();
|
|
165
|
+
}
|
|
166
|
+
catch (e) {
|
|
167
|
+
lastErr = e;
|
|
168
|
+
if (attempt < retries) {
|
|
169
|
+
await new Promise((r) => setTimeout(r, 500 * (attempt + 1)));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=fetch-docs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-docs.js","sourceRoot":"","sources":["../src/fetch-docs.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "basedoc-dameng-mcp",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "面向达梦数据库 (DM8) 的只读 MCP 服务:受限 SQL 查询 + 本地 Markdown 手册离线全文检索(list_manuals / list_sections / read_section / lookup_docs)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "bravejack",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"documentation"
|
|
25
25
|
],
|
|
26
26
|
"bin": {
|
|
27
|
-
"basedoc-dameng-mcp": "dist/server.js"
|
|
27
|
+
"basedoc-dameng-mcp": "dist/server.js",
|
|
28
|
+
"basedoc-dameng-fetch-docs": "dist/bin-fetch.js"
|
|
28
29
|
},
|
|
29
30
|
"main": "dist/server.js",
|
|
30
31
|
"files": [
|
|
@@ -33,7 +34,7 @@
|
|
|
33
34
|
"LICENSE"
|
|
34
35
|
],
|
|
35
36
|
"scripts": {
|
|
36
|
-
"build": "tsc -p tsconfig.json && chmod +x dist/server.js",
|
|
37
|
+
"build": "tsc -p tsconfig.json && chmod +x dist/server.js dist/bin-fetch.js",
|
|
37
38
|
"test": "vitest run",
|
|
38
39
|
"test:watch": "vitest",
|
|
39
40
|
"start": "node dist/server.js",
|
|
@@ -42,10 +43,12 @@
|
|
|
42
43
|
"dependencies": {
|
|
43
44
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
44
45
|
"dmdb": "1.0.48286",
|
|
46
|
+
"turndown": "7.2.4",
|
|
45
47
|
"zod": "4.4.3"
|
|
46
48
|
},
|
|
47
49
|
"devDependencies": {
|
|
48
50
|
"@types/node": "22.10.2",
|
|
51
|
+
"@types/turndown": "5.0.6",
|
|
49
52
|
"typescript": "5.7.2",
|
|
50
53
|
"vitest": "2.1.8"
|
|
51
54
|
},
|