- name
- elf-mcp-knowledge
- description
- Elf Express「新增知識庫到 MCPJungle」的主流程規範:把一套冷門/內部知識(如 SqlSugar、FcDesigner)做成 docs-mcp-server
的語料(corpora/<id>/ + corpus.json + capability),或在必要時做成獨立知識 MCP server,再以 mcpjungle/servers/<id>.json
註冊到唯一入口 MCPJungle gateway、驗證 gateway 真的曝露出工具、並同步更新文件。當任務涉及新增或修改語料、corpus.json、
capabilities(cheatsheet / examples / symbol)、docs_* 工具、語料或 server 命名、servers/*.json、REGISTER_LIST、
「讓 AI 查得到某份文件 / 某個框架的知識」時觸發。
- metadata
- {"version":"1.1.1","owner":"Elf Express"}
# 新增知識庫到 MCPJungle(Elf Express)
> 參考實作:`elf-express/mcp-library` —— `mcp/docs-mcp-server/`(多語料知識 server)+ `mcpjungle/`(gateway 與註冊)。
> 路徑在 `fb1d578` 之後全部收進 `mcp/`(legacy server 在 `mcp/legacy/`)。
> 使用情境:**一台 MCPJungle gateway,底下掛很多「知識型」MCP server / 語料;AI 工具只連 gateway。**
相關 skill:
- **`elf-mcp-book`** — 知識來源是「一本書」(`knowledge.books/` 的素材)時**先看它**:素材怎麼放、四類衍生物哪些不上架、
`sources.json` 怎麼從 front matter 產生、標題可辨識性。本 skill 負責素材挑好之後的上架流程
- `elf-mcp-gateway` — gateway 部署、registrar、nginx、GHCR、安全(本 skill 第 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_*` 工具)
- 新增 / 修改 `mcpjungle/servers/*.json`、`REGISTER_LIST`
- 檢查「gateway 上看不到新語料 / 工具」
### 先選路線(決策樹)
| 情況 | 路線 | 要改程式碼嗎 |
|---|---|---|
| 來源是一本書 / 一份線上文件的擷取產物(`knowledge.books/` 底下) | **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:`servers/<id>.json` 的 `url` 是 `http://docs-mcp-server:5690/mcp/<id>`,gateway 工具名是 `<id>__docs_search`;任一處不同就對不上。
2. **MUST** id 用小寫 `[a-z0-9-]`,以字母開頭,**不含底線**。
WHY:MCPJungle `validateServerName` 只允許 `^[a-zA-Z0-9_-]+$`、禁止 `__`、禁止結尾 `_`(`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 的 `mcp/docs-mcp-server/corpora/README.md`(唯一權威版本)。
WHY:`.` 不在 MCPJungle 允許字元內,`_` 會和 `__` 切分衝突;語言放後綴讓同書各語言相鄰、AI 從 id 就知道語言。
3. **MUST** id 在「整台 gateway」唯一,不只是 `corpora/` 內唯一。
現有保留名:`docs`(策略 B 整包)、`sqlsugar-zh-tw`、`fc-zh-tw`、`opnsense-en`、`opnsense-zh-tw`、`filesystem`、`fetch`、`time`,以及 DB 裡任何已註冊的名字(舊名 `sqlsugar`、`fc` 已改名,gateway 需先 `deregister` 舊名)。
注意:`main` 的 `corpora/` 目前**只有** `fc-zh-tw`、`sqlsugar-zh-tw`;`opnsense-*` 只在 `feat/corpora-naming` 分支(`corpora/README.md` 的「現有語料」表描述的是該分支)。兩個 opnsense id 仍視為**已保留**,不要拿去命名別的書。
WHY:重名註冊會報 `duplicate key value violates unique constraint "idx_mcp_servers_name" (SQLSTATE 23505)`;`servers/*.json` 看不出 DB 實際有什麼,要以 `list servers` 為準。
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:`mcp/docs-mcp-server/src/index.ts` 註解「語料是參數不是新工具,故工具數恆為 8」;工具數隨語料膨脹會吃爆 AI 的工具清單。
### 語料內容
6. **MUST** 每篇 md 以 `# 標題` 開頭,前 15 行內放來源:`> 📖 官方文件:[文字](https://…)` 或 `> Source: https://…`。
WHY:`extractSourceUrl` 只掃前 15 行、只認這兩種格式(`corpus.ts`);抽不到就沒有來源連結,AI 無法引用官方文件。`sources.json` 只用於覆寫。
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** 範例原始碼放 `corpora/<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 檔案;機密掃描對 `examples/`、`notes/` **整個排除**,放進去的機密不會被抓到,只能靠人。
11. **MUST** 分類用一層子目錄(`開發文檔/xx.md`),檔名前綴編號(`01xxx.md`)保持順序。
WHY:`docs_outline` 以頂層目錄分組、`localeCompare("zh-Hant")` 排序;多層目錄只會被歸到第一層。
### 註冊與部署
12. **MUST** 每個新語料新增 `mcpjungle/servers/<id>.json`(策略 A,範本 `templates/servers-corpus.json`),`description` 列出**該語料實際有效的所有工具**。
WHY:gateway dashboard 與 AI 端只看得到這段描述(證據 E10:`fc.json` 漏列 outline / symbol)。
13. **MUST** 把 `<id>` 加進 `mcpjungle/registrar.sh` 的 `REGISTER_LIST` 預設值;若也要進「現有 gateway」,同步改 `docker-compose.dockhand.yml` 的 `REGISTER_LIST` 預設。
WHY:registrar 只註冊清單裡的名字;`servers/*.json` 會被 `Dockerfile.registrar` 的 `COPY servers/ /configs/` 烤進 image,**但不在清單就不會註冊**。
14. **MUST** 修改既有 server 的 `url` / `description` 時,先 `mcpjungle deregister <id>` 再重新註冊。
WHY:`registrar.sh` 的 `is_registered` 看到同名就「略過」,改過的 json 不會生效。
---
## 3. 標準範本與步驟(新增知識庫 Step-by-step)
範本檔:`templates/corpus.json`、`templates/doc-template.md`、`templates/servers-corpus.json`、`templates/corpus-smoke.test.ts`。
```text
Step 0 選 id、查撞名
Step 1 建 corpora/<id>/ 與 md
Step 2 corpus.json(+選用 sources.json / examples/)
Step 3 本機驗證(test / build / stdio / http)
Step 4 servers/<id>.json + REGISTER_LIST
Step 5 部署(build 法或 pull 法)
Step 6 驗證 gateway 真的曝露工具
Step 7 同 PR 更新文件
```
**Step 0 — 選 id、查撞名**
```bash
ls mcp/docs-mcp-server/corpora/ mcpjungle/servers/
docker exec mcpjungle-server /mcpjungle list servers # 以 gateway 執行期清單為準
```
**Step 1 — 語料資料夾**(`mcp/docs-mcp-server/corpora/<id>/`)
```text
corpora/<id>/
├── corpus.json # 必放(欄位全選填,但團隊規定 title / description / capabilities 都要寫)
├── 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 }
}
```
六個欄位**都要寫**(`corpora/README.md` 第三節的團隊規定);`book` / `language` / `source` 程式不讀,但它們是回溯來源與分辨同書各語言的唯一依據。
上游授權要求保留聲明時另加 `license` 欄並在語料根目錄放 `LICENSE` 全文。
能力對照(目前:`sqlsugar-zh-tw` = cheatsheet + examples;`fc-zh-tw` = symbol):
| 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 / 組件名以 `#`/`##`/`###` 標題呈現 |
**Step 3 — 本機驗證**(`cd mcp/docs-mcp-server`;PowerShell 用 `$env:X="..."`)
```bash
npm install && npm run build && npm test # 既有 57 個測試必須全過,數量不得減少
cp <skill>/templates/corpus-smoke.test.ts tests/<id>.test.ts # 把 <corpus-id> 換掉後跑
$env:DOCS_SCOPE="<id>"; npm run dev # stdio 鎖單一語料,stderr 應印「已鎖定單一語料:<id>」
$env:TRANSPORT="http"; npm start # 另開終端:
curl http://localhost:5690/health # corpora / docs 數要 +1 / +篇數
```
**Step 4 — 註冊檔**(`mcpjungle/servers/<id>.json`,範本 `templates/servers-corpus.json`)
```json
{
"name": "<id>",
"transport": "streamable_http",
"description": "<title>:<一句說明>(docs_list_corpora / docs_search / docs_read / docs_outline<依能力追加 / docs_cheatsheet / docs_code_search / docs_code_read / docs_symbol>)",
"url": "http://docs-mcp-server:5690/mcp/<id>"
}
```
再改 `mcpjungle/registrar.sh`:
```sh
# 現行預設值(registrar.sh)是 sqlsugar-zh-tw fc-zh-tw filesystem fetch time —— 把 <id> 插進去:
LIST="${REGISTER_LIST:-sqlsugar-zh-tw fc-zh-tw <id> filesystem fetch time}"
```
`docker-compose.dockhand.yml` 的預設值只有兩本書(`sqlsugar-zh-tw fc-zh-tw`,接現有 gateway 時用),要加也在那裡各自維護。
策略 B(`docs-all.json` → `docs__docs_search`)不需要任何改動,新語料自動出現在 `corpus` 參數。
**Step 5 — 部署**:(現況)push main → `docker-publish.yml` 重建 `docs-mcp-server` 與 `docs-registrar` 兩個 image(語料打包在 image 內)。
build 法 `docker compose up -d --build`;pull 法 `docker compose -f docker-compose.pull.yml up -d`。細節見 `elf-mcp-gateway`。
**Step 6 — 驗證 gateway**(三項都要貼輸出到 PR)
```bash
docker logs <registrar 容器> # 應有「>> 註冊 <id>」且 Exited (0)
docker exec mcpjungle-server /mcpjungle list servers # 有 <id>
docker exec mcpjungle-server /mcpjungle list tools | grep '<id>__' # 無條件工具 4 個 + 能力工具
docker exec mcpjungle-server /mcpjungle invoke <id>__docs_list_corpora --input '{}'
docker exec mcpjungle-server /mcpjungle invoke <id>__docs_search --input '{"query":"<該領域關鍵字>"}'
```
`docs_list_corpora` 在單書端點只會列 `<id>` 自己,且標題行要帶正確 badge(速查表 / 代碼範例 / 符號查)。
**Step 7 — 同 PR 更新文件**(證據 E9–E11:每次都有人漏)
| 檔案 | 更新什麼 |
|---|---|
| 根 `README.md` | 結構表的語料清單與篇數 |
| 根 `CLAUDE.md` | 「目前:sqlsugar 開 cheatsheet+examples、fc 開 symbol…」那行、registrar 預設清單 |
| `mcp/docs-mcp-server/README.md` | 種子語料清單與篇數、工具表 |
| `mcp/docs-mcp-server/corpora/README.md` | 第一節「現有語料」表(id / book / language / 篇數 / capabilities)與第六節的總量數字 |
| `mcpjungle/README.md` | `REGISTER_LIST` 預設值、策略 A 範例 |
| `servers/<id>.json` | description 列齊工具 |
硬化環境(docs-mcp 設了 `DOCS_MCP_AUTH_TOKEN`)改用 `templates/servers-corpus-hardened.json`(`bearer_token: "${DOCS_MCP_AUTH_TOKEN}"`,**不寫明文**)。
### 路線 C:獨立知識 MCP server 掛到 gateway(摘要)
1. 依 `elf-mcp-server` 建 server(`<server-name>` kebab-case、工具 `<domain>_<verb>`、雙 transport、`/health`)。
2. 在 `mcpjungle/docker-compose.mcpjungle.yml` 加 service:`networks: [mcpjungl]`、**不發佈 ports**、`TZ`、`TRANSPORT: http`。
3. `servers/<server-name>.json` 用 `elf-mcp-gateway` 的 `templates/servers/http-server.json`,`url: http://<container-name>:<PORT>/mcp`。
4. 加進 `REGISTER_LIST`、`docker-publish.yml` / `ci.yml` / `dependabot.yml` matrix(`elf-mcp-gateway` 規則 23)。
5. Step 6 驗證方式相同:`list tools | grep '<server-name>__'` + 一次 `invoke`。
---
## 4. 禁止簡化
View on GitHub