- name
- elf-mcp-knowledge
- description
- Elf Express「新增知識庫到 MCPJungle」的主流程規範:把一套冷門/內部知識(如 SqlSugar、FcDesigner)做成 docs-mcp-server
的語料(mcpjungle/books/<書名>/corpus/<id>/ + corpus.json + capability),或在必要時做成獨立知識 MCP server,再由 registrar 依 corpus.json
自動註冊到唯一入口 MCPJungle gateway、驗證 gateway 真的曝露出工具、並同步更新文件。當任務涉及新增或修改語料、corpus.json、
capabilities(cheatsheet / examples / symbol)、docs_* 工具、語料或 server 命名、gen-book-configs.mjs、registry/*.json、REGISTER_LIST、
「讓 AI 查得到某份文件 / 某個框架的知識」時觸發。
- metadata
- {"version":"1.2.3","owner":"Elf Express"}
# 新增知識庫到 MCPJungle(Elf Express)
> 參考實作:`elf-express/mcp-library` —— `mcpjungle/docs-mcp-server/`(多語料知識 server)+ `mcpjungle/books/`(書與語料)
> + `mcpjungle/registry/`(registrar 與註冊設定)+ `mcpjungle/gateway/`(gateway)。量測基準:`origin/release` @ `73b8206`。
> legacy server 已於 `0a37607` 刪除(E-126;歷史內容用 `git show 0a37607^:<路徑>` 讀)。
> 使用情境:**一台 MCPJungle gateway,底下掛很多「知識型」MCP server / 語料;AI 工具只連 gateway。**
相關 skill:
- **`elf-mcp-book`** — 知識來源是「一本書」(`mcpjungle/books/<書名>/source/` 的素材)時**先看它**:素材在 `source/`、四類衍生物哪些不上架、
`import.ts` 要做到什麼、`sources.json` 怎麼從 front matter 產生(舊式語料)、上架前檢查(來源與標題可辨識性)。本 skill 負責素材挑好之後的上架流程
- `elf-mcp-gateway` — gateway 部署、registrar、nginx、GHCR、安全(本 skill 第 4 步〈註冊〉、第 5–7 步會用到)
- `elf-mcp-server` — 語料模式放不下時,從零寫一個知識 MCP server 的範本與禁止簡化規則
- `elf-unit`(測試放置 / 覆蓋率)、`elf-stack`(版本)、`elf-cicd-docker`(映像)
- 第三方通用 skill `mcp-builder`(Anthropic):通用 MCP 設計原則;本 skill 是它之上的團隊具體層,**衝突時以本 skill 為準**
---
## 1. 何時使用
- 「讓 AI 查得到 X 的文件」「把 Y 框架的筆記掛上 MCP」「新增一本書 / 一個語料」
- 修改既有語料的 md、`corpus.json`、`sources.json`、`examples/`
- 為語料開新能力(cheatsheet / examples / symbol)或新增一種能力(新 `docs_*` 工具)
- 書本註冊(registrar 依 `corpus.json` 自動產生);新增 / 修改非書本註冊檔 `mcpjungle/registry/*.json`、`REGISTER_LIST`
- 檢查「gateway 上看不到新語料 / 工具」
### 先選路線(決策樹)
| 情況 | 路線 | 要改程式碼嗎 |
|---|---|---|
| 來源是一本書 / 一份線上文件的擷取產物(`mcpjungle/books/<書名>/source/` 底下) | **A. 新增語料**,但**先讀 `elf-mcp-book`** 挑哪一份、排除哪些 | 否 |
| 知識是 markdown(可轉成 md)+可選的範例原始碼 | **A. 新增語料**(預設,90% 的情況) | 否 |
| 語料需要現有能力以外的查詢方式(例:依版本號查、表格欄位查) | **B. 新增 capability + 一個 `docs_*` 工具** | 是,見 `references/add-capability.md` |
| 需要即時資料(DB / 外部 API)、非文字資料、或必須另一個 runtime | **C. 獨立知識 MCP server** | 是,照 `elf-mcp-server` |
**MUST NOT** 為了一本新書另寫一個 server(路線 C)——`docs-mcp-server` 的設計主軸就是「新增一本書 = 丟資料夾 + corpus.json,不改 `.ts`」。
---
## 2. 固定規則
### 命名(一個 gateway 底下很多 server 共存的關鍵)
1. **MUST** 語料 id = 資料夾名 = gateway server 名(策略 A)= HTTP 路徑 `/mcp/<id>`。四者**同一個字串**。
WHY:registrar 產生的註冊設定 `url` 是 `http://docs-mcp-server:5690/mcp/<id>`(`gen-book-configs.mjs:63`),gateway 工具名是 `<id>__docs_search`;任一處不同就對不上。
2. **MUST** id 用小寫 `[a-z0-9-]`,以字母開頭,**不含底線**。
WHY:MCPJungle `validateServerName` 只允許 `^[a-zA-Z0-9_-]+$`、禁止 `__`、禁止結尾 `_`(`mcpjungle/gateway/internal/service/mcp/util.go`),
因為工具以 `<server>__<tool>` 切分;不用底線就永遠不會踩到。`getCorpus` 比對不分大小寫,但 URL 與 gateway 名稱分大小寫,一律小寫才不會出現兩套。
2a. **MUST** 語料 id 格式為 **`<書名>-<語言>`**(使用者決議,2026-09-27):
| 語言後綴 | 意思 | 使用時機 |
|---|---|---|
| `-en` | 英文 | 英文原文 |
| `-zh-tw` | 繁體中文 | 中文原文或繁中譯本 |
| `-zh-cn` | 簡體中文 | 簡中原文 |
| `-bi` | 中英混排 | **只有**來源本身中英混排、無法拆開時 |
- 同一本書有兩種語言 = **兩個語料**(`opnsense-en`、`opnsense-zh-tw`),按字母排序自然相鄰(「書放在一起」)。**MUST NOT** 再放第三份雙語版(同內容存三份、搜三次)。
- 書名本身可含 `-`(`vue-router-en`);分辨書名與語言靠 `corpus.json` 的 `book` / `language` 欄位,**不靠**拆字串。
- id 總長 **≤ 30 字元**,因 gateway 工具名為 `<id>__<tool>`,部分用戶端上限約 64 字元。
- `corpus.json` **MUST** 含 `book`、`language`(BCP 47,如 `zh-TW`)、`source`(原始網址)、`title`、`description`、`capabilities`。
- 目錄樹與完整範例見 repo 的 `mcpjungle/books/README.md`(唯一權威版本;命名在第一節 `:12-45`,目錄樹在第二節)。
`:12-45` 是整個第一節:`:12-34` 是命名規則(語言後綴表與規則表),`:36-45` 是「現有語料」表。
WHY:`.` 不在 MCPJungle 允許字元內,`_` 會和 `__` 切分衝突;語言放後綴讓同書各語言相鄰、AI 從 id 就知道語言。
3. **MUST** id 在「整台 gateway」唯一,不只是 `mcpjungle/books/` 內唯一。
現有保留名(`73b8206`):6 個語料 id `fc-zh-tw`、`nginx-en`、`opnsense-en`、`opnsense-zh-tw`、`sqlsugar-zh-tw`、`stalwart-en`(`books/README.md:38-45`);`filesystem`、`fetch`、`time`(`mcpjungle/registry/*.json`);`docs`(策略 B 整包,`mcpjungle/registry/optional/docs.json`);以及 DB 裡任何已註冊的名字(舊名 `sqlsugar`、`fc` 已改名,gateway 需先手動 `deregister` 舊名,mcp-library `mcpjungle/README.md:113`)。
WHY:重名註冊會報 `duplicate key value violates unique constraint "idx_mcp_servers_name" (SQLSTATE 23505)`;repo 內的設定(`corpus.json`、`registry/*.json`)看不出 DB 實際有什麼,要以 `list servers` 為準(mcp-library `CLAUDE.md:132`)。
`gen-book-configs.mjs:40` 擋語料 id 重複,registrar 也擋 `registry/*.json` 與語料 id 同名(`registrar.sh:4`、`:32-35`);兩者都只看 repo,擋不到 DB 裡的舊名。
4. **MUST** 用領域名(`sqlsugar`、`furion`、`fc`),**MUST NOT** 用 `docs`、`notes`、`kb`、`test`、`new` 這種泛名。
WHY:AI 端看到的工具是 `<id>__docs_search`,id 就是 AI 選工具的唯一線索。
5. **MUST NOT** 為語料新增工具名(例如 `furion_search`)。工具固定 `docs_*` 8 個,語料是**參數**。
WHY:`mcpjungle/docs-mcp-server/src/index.ts:5` 註解「語料是參數不是新工具,故工具數恆為 8」;工具數隨語料膨脹會吃爆 AI 的工具清單。
### 語料內容
6. **MUST** 每篇 md 以 `# 標題` 開頭,前 15 行內放來源:`> 📖 官方文件:[文字](https://…)` 或 `> Source: https://…`。
WHY:`extractSourceUrl` 只掃前 15 行,只看以 `>` 開頭或含 source/来源/來源/官方 字樣的行,先取 markdown 連結、否則取裸網址(`mcpjungle/docs-mcp-server/src/corpus.ts:326-342`,註解列的就是這兩種格式);抽不到就沒有來源連結,AI 無法引用官方文件。`sources.json` 只用於覆寫(優先於自動抽取,`books/README.md:187`)。
front matter 的 `source: https://…`(不帶引號)也抽得到,所以 `import.ts` 產生、以 front matter 開頭再接 `# 標題` 的 md 也符合本條(`books/README.md:193`);帶引號會把 `"` 吃進網址,見 `elf-mcp-book` 規則 9。
7. **MUST** 開 `cheatsheet` 的語料,每篇要有標題含「速查」的段落(慣例 `## 速查表`,其上一行放 `[//]: # (<id>-cheatsheet)` 標記)。
WHY:`extractCheatsheet` 以 `^#{1,6}\s+.*速查` 找段落,切到下一個同級標題;沒有就回「沒有速查表段落」。
8. **MUST** 開 `symbol` 的語料,API / 組件名要寫成 `#`/`##`/`###` 標題(`####` 以下不進索引)。
WHY:`buildSymbolIndex` 用 `extractHeadings(content, 1)`,只收 1–3 級;U+200B/U+FEFF 已自動清掉。
9. **MUST** 範例原始碼放 `mcpjungle/books/<書名>/corpus/<id>/examples/`,副檔名限 `.cs .csproj .sln .json .ts .js`,並在 `corpus.json` 開 `examples: true`。
WHY:`listCodeFiles` 用白名單 `CODE_EXT`、略過 `bin/obj/.vs`;`listMarkdownFiles` 會跳過 `examples/`,兩者不互相污染。
10. **MUST** 單檔 < 5MB、不含真實連線字串 / 密碼 / token。
WHY:CI `basics` 擋 >5MB 檔案(`ci.yml:77`);機密掃描對 `examples/` **整個排除**(`ci.yml:95`;`notes/` 在 `73b8206` 已不在排除清單),放進 `examples/` 的機密不會被抓到,只能靠人。
11. **MUST** 分類用一層子目錄(`開發文檔/xx.md`),檔名前綴編號(`01xxx.md`)保持順序。
WHY:`docs_outline` 以頂層目錄分組、`localeCompare("zh-Hant")` 排序;多層目錄只會被歸到第一層。
### 註冊與部署
12. **MUST** 新語料不手寫註冊檔:registrar 依 `corpus.json` 自動產生,見 `elf-mcp-gateway` 第 2 節〈註冊〉。本機先跑 `node mcpjungle/registry/gen-book-configs.mjs mcpjungle/books <暫存目錄>`,印出的 id 要包含新語料。以 `elf-mcp-gateway` 第 2 節〈註冊〉A 步驟 1、2 為準。
WHY:產生器依 capabilities 自動列齊工具(`gen-book-configs.mjs:57,62`),取代證據 E10 那種手寫漏列;任一語料不合法,registrar 不註冊任何 server 並以 1 結束(`:87-90`)。
(`:87-90` 是產生器自己以 1 結束;registrar 收到後呼叫 `fail` 整批不註冊,見 `registrar.sh:24,27`、`books/README.md:171`。)
gateway dashboard 與 AI 端只看得到這段描述(E10:當時手寫的 fc 註冊檔漏列 outline / symbol),所以描述的來源 `corpus.json` 的 `description` 要寫清楚涵蓋範圍。
13. **MUST NOT** 為了新語料去改 `REGISTER_LIST`:它空白時 registrar 註冊全部書本+`registry/*.json`,有值時只註冊它列的名字(`registrar.sh:44-52`;`REGISTER_EXTRAS=0` 時空白只註冊書本,`compose.attach.yaml:25` 預設 0)。細節見 `elf-mcp-gateway` 第 2 節〈註冊〉。以 `elf-mcp-gateway` 規則 14、規則 10 為準。
WHY:書本清單由 `gen-book-configs.mjs` 掃 `books/*/corpus/*/` 產生,registrar 映像也直接帶 `books/`(`mcpjungle/registry/Dockerfile:5`);把新 id 寫進 `REGISTER_LIST` 只會讓它變成「只註冊這些」,**清單外的書反而不會註冊**。
14. **MUST** 修改既有 server 的 `url` / `description` 時,先 `mcpjungle deregister <id>` 再重新註冊。
WHY:`registrar.sh` 的 `is_registered` 看到同名就「已註冊,略過」(`registrar.sh:68-75`),改過的設定不會生效。改名既有語料時 registrar 只新增、不移除,舊名要手動 `deregister`(`books/README.md:179`)。
---
## 3. 標準範本與步驟(新增知識庫 Step-by-step)
範本檔:`templates/corpus.json`、`templates/doc-template.md`、`templates/corpus-smoke.test.ts`。
`templates/servers-corpus.json`、`templates/servers-corpus-hardened.json` 是舊式手寫註冊檔格式,僅供對照;現行由 `gen-book-configs.mjs:59-65` 產生(兩者的差異見第 7 節待確認 9)。
```text
Step 0 選 id、查撞名
Step 1 建 mcpjungle/books/<書名>/corpus/<id>/ 與 md
Step 2 corpus.json(+選用 sources.json / examples/)
Step 3 本機驗證(test / build / stdio / http + 書本的上架前檢查)
Step 4 註冊(registrar 依 corpus.json 自動產生;本機用 gen-book-configs.mjs 驗證)
Step 5 部署(build 法或 pull 法)
Step 6 驗證 gateway 真的曝露工具
Step 7 同 PR 更新文件
```
**Step 0 — 選 id、查撞名**
在 mcp-library repo 根目錄執行(Step 0–2 都在根目錄;Step 3 才 `cd mcpjungle/docs-mcp-server`):
```bash
ls mcpjungle/books/*/corpus/ mcpjungle/registry/
docker compose exec mcpjungle /mcpjungle list servers # 以 gateway 執行期清單為準(books/README.md:163)
```
**Step 1 — 語料資料夾**(`mcpjungle/books/<書名>/corpus/<id>/`)
來源是一本書時,md 由 `import.ts` 產生,照 `elf-mcp-book` 第 3 節;下面是語料資料夾本身的內容。
```text
mcpjungle/books/<書名>/corpus/<id>/
├── corpus.json # 必放(docs-mcp-server 讀取時欄位全選填,但團隊規定六欄 book / language / source / title / description / capabilities 都要寫;book 要等於書資料夾名)
├── sources.json # 選用:只在要覆寫自動抽取的來源時放
├── index.md # 選用:分類導航
├── <分類>/01<主題>.md # 依 templates/doc-template.md
└── examples/ # 僅 capabilities.examples=true 時
```
**Step 2 — `corpus.json`**(`templates/corpus.json`)
```json
{
"book": "<書名,小寫、同一本書各語言共用>",
"language": "<BCP 47:en / zh-TW / zh-CN>",
"source": "https://<原始網址>",
"title": "<人類可讀名稱,用該語料的語言寫>",
"description": "<一句話:這是什麼、涵蓋哪些主題、附不附程式碼範例、有沒有已知缺陷(機器翻譯、失效圖片連結…)>",
"capabilities": { "cheatsheet": false, "examples": false, "symbol": false }
}
```
六個欄位**都要寫**(`mcpjungle/books/README.md` 第三節 `:112-155` 的團隊規定);`source` 程式不讀;`book`/`language` docs-mcp-server 不讀,但 registrar 的 `gen-book-configs.mjs:52-55` 會驗證(`book` 要等於書資料夾名、id 要等於 `<book>-<language 小寫>` 或 `<book>-bi`),不符就整批不註冊(`registrar.sh:24,27`)。三者是回溯來源與分辨同書各語言的唯一依據。
上游授權要求保留聲明時另加 `license` 欄並在語料根目錄放 `LICENSE` 全文。
能力對照(目前,`73b8206`:`sqlsugar-zh-tw` = cheatsheet + examples;`fc-zh-tw`、`nginx-en`、`opnsense-en`、`opnsense-zh-tw`、`stalwart-en` = symbol,`books/README.md:38-45`):
| capability | 能回內容的工具(未啟用時仍曝露,呼叫會回提示) | 語料需要具備 |
|---|---|---|
| (不需 capability) | `docs_list_corpora` `docs_search` `docs_read` `docs_outline` | md 檔 |
| `cheatsheet` | `docs_cheatsheet` | 每篇有「速查」標題段落 |
| `examples` | `docs_code_search` `docs_code_read` | `examples/` 下有白名單副檔名原始碼 |
| `symbol` | `docs_symbol` | API / 組件名以 `#`/`##`/`###` 標題呈現 |
出處(mcp-library `origin/release` @ `2f71743`;`docs-mcp-server/src/` 自 `73b8206` 起未變,行號兩者通用):8 個工具**一律無條件註冊**(`mcpjungle/docs-mcp-server/src/index.ts:145-279`,8 次 `registerTool` 都沒有條件;同檔 `:5` 註解「故工具數恆為 8,capability-gated」),所以 **capability 決定的是「能不能回內容」,不是「工具會不會出現」**;capability 沒開時工具照樣可呼叫,只是回一段文字提示、不是錯誤:
未啟用時回提示的位置(`mcpjungle/docs-mcp-server/src/corpus.ts`):
- `cheatsheet` → `:545`「未啟用速查表功能。請改用 docs_read …」(gate 在 `:544`)
- `examples` → `:572`「無代碼範例(未啟用 examples)。請改用 docs_search …」(gate 在 `:571`;`docs_code_search` 走 `:579`、`docs_code_read` 走 `:639`,兩個工具共用同一段 `requireExamples`)
- `symbol` → `:729`「未啟用 symbol(無符號索引)。請改用 docs_search 或 docs_outline。」(gate 在 `:728`)
**Step 3 — 本機驗證**(`cd mcpjungle/docs-mcp-server`;PowerShell 用 `$env:X="..."`,mcp-library `CLAUDE.md:50-55` 也是這個寫法)
```bash
npm ci && npm run build && npm test # 既有測試(73b8206 為 128 個)必須全過,數量不得減少
cp <skill>/templates/corpus-smoke.test.ts tests/<id>.test.ts # 即 mcpjungle/docs-mcp-server/tests/<id>.test.ts;把 <corpus-id> 換掉後跑
$env:DOCS_SCOPE="<id>"; npm run dev # stdio 鎖單一語料,stderr 應印「已鎖定單一語料:<id>」
# (PowerShell;Git Bash 寫成 DOCS_SCOPE=<id> npm run dev)
$env:TRANSPORT="http"; npm start # 另開終端:
# (PowerShell;Git Bash 寫成 TRANSPORT=http npm start)
curl http://localhost:5690/health # corpora / docs 數要 +1 / +篇數
```
書本語料另跑 `elf-mcp-book` 第 3 節的「上架前檢查(規則 9、10、11)」:泛名標題 0 篇、來源問題 0 篇。
**Step 4 — 註冊**:註冊見 `elf-mcp-gateway` 第 2 節〈註冊〉。下面兩步即該節 A 的步驟 1、2,指令、預期與停止條件跟該節逐字一致;兩步都不起容器。
下面兩步在 mcp-library repo 根目錄執行(Step 3 停在 `mcpjungle/docs-mcp-server`,先 `cd ../..`):
- **步驟 1(不手寫註冊檔)**:不新增任何註冊檔,`REGISTER_LIST` 保持空白(預設)。
指令 `docker compose config | grep REGISTER_LIST`,預期 `REGISTER_LIST: ""`(接現有 gateway 時加 `-f compose.attach.yaml`);GitOps 工具則看它的 env 編輯器有沒有設 `REGISTER_LIST`。
有值就停,見 `elf-mcp-gateway`〈註冊〉B 表第 2 列。
- **步驟 2(部署前驗證)**:
指令 `node mcpjungle/registry/gen-book-configs.mjs mcpjungle/books "$(mktemp -d)"`,預期 exit 0,印出的 id 包含 `<id>`(`73b8206` 印出 6 個 id)。
GitHub에서 보기