Skip to main content

elf-mcp-server

Elf Express 撰寫 / 修改 MCP server(TypeScript + @modelcontextprotocol/sdk)的團隊規範與完整範本:專案結構、工具命名與描述、 zod 輸入 schema、唯讀 annotations、錯誤以文字結果回傳、輸出截斷、stdio 與 Streamable HTTP 雙 transport、Bearer token、 /health、stderr 日誌、測試、Dockerfile,以及「禁止簡化」清單。當任務涉及新建 MCP server、修改 src/index.ts 的 registerTool、 tool description / inputSchema、http.ts transport、MCP 相關測試或 Dockerfile,或重構 / 精簡既有 MCP server 程式碼時觸發。

소스 정보

저장소
elf-express/skillshare-source
최근 소스 활동
2026년 10월 4일 05:10
감지된 SKILL.md 언어
중국어
스타
0
포크
0

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

파일 탐색기
13 개 파일

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
elf-mcp-server
description
Elf Express 撰寫 / 修改 MCP server(TypeScript + @modelcontextprotocol/sdk)的團隊規範與完整範本:專案結構、工具命名與描述、 zod 輸入 schema、唯讀 annotations、錯誤以文字結果回傳、輸出截斷、stdio 與 Streamable HTTP 雙 transport、Bearer token、 /health、stderr 日誌、測試、Dockerfile,以及「禁止簡化」清單。當任務涉及新建 MCP server、修改 src/index.ts 的 registerTool、 tool description / inputSchema、http.ts transport、MCP 相關測試或 Dockerfile,或重構 / 精簡既有 MCP server 程式碼時觸發。
metadata
{"version":"1.1.0","owner":"Elf Express"}
# MCP Server 撰寫規範(Elf Express) > 參考實作:`elf-express/mcp-library` 的 `mcp/docs-mcp-server/`(核心、多語料)、`mcp/legacy/sqlsugar-mcp/sqlsugar-mcp-server/` 與 > `mcp/legacy/fc-designer-mcp/`(legacy,工具描述寫得最完整)。路徑在 `fb1d578` 之後全部收進 `mcp/`,legacy 另歸 `mcp/legacy/`(不在根 compose 堆疊)。完整範本在 `templates/`,已在 Node 24.18 下實測 `tsc` 通過、9 個測試通過、stdio 可呼叫工具。 相關 skill: - **`elf-mcp-book`** — 來源是「一本書」時的素材與上架規範(`knowledge.books/`) - **`elf-mcp-knowledge`** — 「新增知識庫」主流程。**要掛新文件 / 新書時先看它**:90% 的情況是新增語料,不需要寫新 server - `elf-mcp-gateway` — 寫好的 server 如何註冊到 MCPJungle、部署、CI - `elf-stack`(版本)、`elf-unit`(測試放置 / 覆蓋率 55%)、`elf-cicd-docker`(映像)、`elf-cicd-review`(PR AI review) - 第三方 `mcp-builder`(Anthropic):通用設計原則;與本 skill 衝突時以本 skill 為準 --- ## 1. 何時使用 - 新建一個 MCP server(只在 `elf-mcp-knowledge` 的決策樹判定為路線 C 時:需要即時資料、非文字資料、另一個 runtime) - 修改任何 MCP server 的 `registerTool`、description、zod schema、handler、`http.ts`、Dockerfile、測試 - 「重構 / 精簡 / 整理」MCP server 程式碼 —— **先讀第 4 節「禁止簡化」** --- ## 2. 固定規則 ### 專案結構與分層 1. **MUST** 三層分檔:`src/index.ts`(工具註冊薄殼+啟動)、`src/http.ts`(transport)、`src/<domain>.ts`(純邏輯,不 import SDK / zod)。 WHY:`docs-mcp-server` 的 `corpus.ts` 可以不起 server 直接單元測試;設計文件 `bc6760c` §8 明定此邊界。 2. **MUST** 純邏輯函式一律回傳 `string`(Markdown),handler 一行 `textResult(doX(...))`。 WHY:邏輯可測、工具殼不長邏輯;現有 8 個工具全是這個形狀。 3. **MUST** 新專案依團隊標準:Node 24.18、pnpm 11.x、TypeScript、`*.test.ts` 與原始碼同目錄、vitest 行覆蓋率 ≥ 55%(範本 `vitest.config.ts`)。 現況落差(**不要順手「統一」,列在待確認**):`docs-mcp-server` 用 npm、`tests/` 目錄、無覆蓋率門檻、Docker `node:26-alpine`、CI `node-version: 20`、`engines >=18`。 ### SDK 與版本(實際在用的) 4. **MUST** 用 `@modelcontextprotocol/sdk` 的高階 API:`McpServer` + `server.registerTool(name, {title, description, inputSchema, annotations}, handler)`; stdio 用 `StdioServerTransport`,HTTP 用 `StreamableHTTPServerTransport`。package.json 寫 `^1.12.0`,三個 server 的 lockfile 實際解析到 **1.29.0**。 5. **MUST** zod 版本在同一個 repo 內一致。現況 `docs-mcp-server`/`sqlsugar` 是 zod 3.25.76、`fc-designer-mcp` 被 dependabot 升到 zod 4.4.3(證據 E3)。新 server 跟 `docs-mcp-server`(zod 3)。 ### 工具設計 6. **MUST** 工具名 `snake_case`、`<domain>_<verb>`(例:`docs_search`、`sqlsugar_read_note`),**不含 `__`**、不以 `_` 結尾、只用 ASCII。 WHY:MCPJungle 以第一個 `__` 切 `<server>__<tool>`;gateway 後面的 AI 看到的是 `<server>__<domain>_<verb>`。 7. **MUST** 一個 server 的工具數**固定**,資料種類(語料 / 書 / 資料集)是**參數**,不是新工具;能力差異用 capability gating。 WHY:`docs-mcp-server` 工具數恆為 8,不隨語料膨脹;gateway 底下掛越多 server,工具總數越要可控。 8. **MUST** description 固定四段:一句用途 →「用途:」何時用、與哪個工具分工 →「參數:」逐項(型別、範圍、預設)→「回傳:」格式 +「範例:」真實值。 有 scope 的 server 追加 `scopeNote`。範本見 `templates/src/index.ts`。 WHY:證據 E6 —— legacy 描述有範例,docs-mcp 的 `docs_code_*` / `docs_symbol` 描述只剩一行參數名。 9. **MUST** 每個輸入欄位:zod 型別+`min`/`max`(字串長度、數字範圍)+`default`(選填數值)+`.describe()`;物件 `.strict()`;schema 放模組層級。 WHY:`dd4853a` 把 schema 從 `createServer` 內移出(每 session 重建);`.strict()` 讓打錯參數名的呼叫直接失敗而不是被默默忽略。 10. **MUST** 唯讀工具帶 `annotations: READ_ONLY`(`readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false`)。有寫入的工具另立 annotations 並在 description 開頭寫明副作用。 11. **MUST** 「找不到 / 未啟用 / 缺參數」回**文字結果**,內容含下一步(改用哪個工具、可用值清單);**MUST NOT** 為這類情況 throw。 只有 transport 層意外才走 JSON-RPC error(`http.ts` 的 500 `-32603`)。WHY:AI 能讀文字自我修正;例外只會變成一行「tool failed」。 12. **MUST** 所有輸出經 `truncateIfNeeded`(`CHARACTER_LIMIT = 25000`),截斷訊息要告訴 AI 怎麼縮小範圍;列表類工具要有 `limit`(`docs_search` 1–50 預設 15、`docs_code_search` 1–30 預設 10、`docs_symbol` 1–30 預設 8)。 13. **MUST** 讀檔只從「目錄列舉結果」中比對(`findNotes` / `findFiles`),**MUST NOT** `path.join(root, userInput)`。WHY:天然防 `../` 穿越;範本有測試 `never escapes the data root`。 ### Transport / 設定 / 日誌 14. **MUST** 同一份程式支援兩種 transport:`TRANSPORT` 未設 = stdio(本機 `.mcp.json` / npx),`TRANSPORT=http` = Streamable HTTP(Docker / gateway)。Dockerfile 內建 `ENV TRANSPORT=http`。 15. **MUST** HTTP 模式:每 session 一個 transport(以 `mcp-session-id` 為鍵、`onclose` 刪除);`POST /mcp` 非 initialize 又無 session → 400;`GET`/`DELETE` 以 session 找 transport;`GET /health` **免驗證**並回載入數量。 16. **MUST** `MCP_AUTH_TOKEN` 有設就要求 `Authorization: Bearer <token>`(401 + JSON-RPC `-32001`);`/health` 永遠公開。 17. **MUST** 所有日誌走 `console.error`(stderr),前綴 `[<server-name>]`。**MUST NOT** 在任何路徑 `console.log`。 WHY:stdio 模式 stdout 就是 JSON-RPC 串流,一行 log 就讓用戶端解析失敗。 18. **MUST** 環境變數一覽寫進 README:`TRANSPORT`、`PORT`、`MCP_AUTH_TOKEN`、資料目錄覆寫(如 `DOCS_CORPORA_DIR`)、scope(如 `DOCS_SCOPE`)。 19. **MUST** 啟動時印出:資料目錄、載入數量、找不到資料時的警告、HTTP 監聽位址、驗證是否啟用(`logStartupInfo` 模式)。 20. `express.json({ limit: "8mb" })` 與 nginx `client_max_body_size 8m` 保持一致(`mcpjungle/nginx.example.conf` 註明對齊)。 ### 時區 21. 容器設 `TZ=${TZ:-Asia/Taipei}`(`bafd8e8`,gateway / docs-mcp / registrar 三者一致)。本 repo 的 server 不寫 DB;**若 server 會讀寫 PostgreSQL,時間一律以 UTC `timestamptz` 儲存(`elf-postgresql`),只在輸出給人看時轉台北時間**——TZ 只影響日誌與顯示。 ### 測試 22. **MUST** 純邏輯每個 `do*` 至少測:命中、未命中(回提示且含下一步)、邊界(空輸入、上限截斷)、gating(若有)、路徑穿越。 23. **MUST** fixture 用 `fs.mkdtempSync` 臨時目錄+環境變數覆寫資料根(`corpus.test.ts` 模式),`afterAll` 還原 env 並刪目錄;快取模組要匯出 `_clearCaches()` 在 `beforeEach` 呼叫。 24. **MUST** 另做一次 stdio 冒煙:build 後送 `initialize` → `tools/list` → `tools/call`(見第 3 節),確認工具數與描述正確。 --- ## 3. 標準範本 `templates/` 內容(全部可直接複製,替換 `<...>`): | 檔案 | 說明 | |---|---| | `package.json` | ESM、`bin`、scripts(build/test/test:coverage)、engines ≥24.18、`packageManager` | | `tsconfig.json` | 與 docs-mcp-server 相同(ES2022 / Node16 / strict),排除 `*.test.ts` | | `vitest.config.ts` | colocated 測試、v8 覆蓋率 lines 55 | | `src/index.ts` | 3 個唯讀工具(list/search/read)、模組層級 strict schema、完整四段描述、雙 transport 啟動 | | `src/http.ts` | Streamable HTTP(session map、/mcp 與 /mcp/:scope、401/404/400/500、/health) | | `src/knowledge.ts` | 純邏輯:資料根解析、列舉、mtime 快取、去 BOM、模糊比對、AND 搜尋、截斷 | | `src/knowledge.test.ts` | 9 個測試:排序、AND、未命中提示、空查詢、讀檔、巢狀、路徑穿越、過濾、截斷 | | `Dockerfile` / `.dockerignore` / `docker-compose.yml` / `.env.example` | 多階段 build、prod-only deps、`TRANSPORT=http`、healthcheck、TZ | 佔位符:`<server-name>`(kebab-case,= image 名 = container 名 = gateway server 名)、`<domain>`(工具前綴,snake_case)、 `<PORT>`(現有:sqlsugar 5688、fc 5689、docs 5690;新 server 往後取號)、`<ENV_DATA_DIR>`、`<data-dir>`、`<NODE_IMAGE_TAG>`、`<PNPM_VERSION>`(見 `elf-stack`)。 stdio 冒煙(bash;PowerShell 改用 legacy `sqlsugar-mcp-server/test-client.mjs` 的 Node 寫法): ```bash pnpm run build (printf '%s\n' \ '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"smoke","version":"1"}}}' \ '{"jsonrpc":"2.0","method":"notifications/initialized"}' \ '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \ '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"<domain>_search","arguments":{"query":"<real keyword>"}}}'; sleep 2) \ | node dist/index.js ``` HTTP 冒煙:`$env:TRANSPORT="http"; pnpm start` 後 `curl http://localhost:<PORT>/health`,再照 `mcp/docs-mcp-server/README.md`「驗證 MCP 流程」的 initialize → 帶 `mcp-session-id` 呼叫。 --- ## 4. 禁止簡化 > 使用者痛點:「AI 越寫越簡單」。以下每條附證據編號(見 `references/simplification-evidence.md`)或現行程式碼位置。 > **任何 PR 讓下列基準值下降,必須在 PR 描述逐條說明並取得人工同意。** 基準值(`mcp/docs-mcp-server`,HEAD `f05dcb1`,2026-10-04 實測未變)——修改前後都跑一次並貼到 PR: ```bash cd mcp/docs-mcp-server grep -c 'registerTool(' src/index.ts # 8 grep -c '\.strict()' src/index.ts # 8 grep -c 'annotations: READ_ONLY' src/index.ts # 8 cat tests/*.ts | grep -cE '^\s*it\(' # 57(2+4+25+7+10+4+5) ``` 1. **MUST NOT** 刪工具、合併工具、或把「收編」說成「簡化」。WHY:證據 E5——收斂成 docs-mcp 時丟了 C# 範例工具與 `include_index`,事後才補。任何工具變動要附「舊工具 → 新位置」對照表。 2. **MUST NOT** 縮短 description、拿掉「參數 / 回傳 / 範例」段落。WHY:證據 E6;gateway 後的 AI 只能靠描述選工具。 3. **MUST NOT** 刪 zod 的 `min`/`max`/`default`/`.describe()`/`.strict()`,或把欄位改成 `z.any()` / `z.string()` 無上限。WHY:輸入上限是唯一的防濫用閘(搜尋 200 字、檔名 300 字、limit ≤ 50)。 4. **MUST NOT** 把友善文字錯誤改成 throw,或把錯誤訊息的「下一步建議」刪掉。WHY:證據 E8,`dd4853a` 補回被寫短的缺 corpus 訊息。 5. **MUST NOT** 為了讓測試通過放寬比對或加 fallback;**MUST NOT** 刪測試、改成 `it.skip`、把斷言改寬(`toMatch(/./)`)。WHY:證據 E7(`e3d8808` 移除讓測試假通過的 token-OR fallback)。 6. **MUST NOT** 移除 `truncateIfNeeded`、mtime 快取、BOM 處理、`SKIP_DIRS`、副檔名白名單。WHY:分別防爆 context、確保改檔即時生效、避免亂碼、避免掃進 `node_modules`/`bin`/`obj`。 7. **MUST NOT** 移除 HTTP 模式的 `/health`、Bearer 驗證、404(未知 scope)、400(無 session)、500 包裝與 `onclose` 清理。WHY:gateway healthcheck、registrar 等待、MCPJungle 連線都依賴這些行為(`mcp/docs-mcp-server/src/http.ts`)。 8. **MUST NOT** 移除 stdio 或 HTTP 任一 transport。WHY:stdio 給本機 `.mcp.json` / `npx -y @elf-express/docs-mcp-server`,HTTP 給 gateway 與遠端連接器(CLAUDE.md「A–D 四種接法」)。 9. **MUST NOT** 用 `console.log`。WHY:stdio 串流污染。 10. **MUST NOT** 只改程式不改檔頭註解 / README / servers/*.json 描述。WHY:證據 E8、E9、E10。 11. **MUST NOT** 讓 dependabot 或「順手升級」跨主版本(zod、express、typescript、@types/node、SDK)而不經人工評估。WHY:證據 E3——拿掉 `semver-major` ignore 後三個 server 主版本分岔。 --- ## 5. 檢查清單 - [ ] 三層分檔;純邏輯不 import SDK / zod - [ ] 每個工具:snake_case `<domain>_<verb>`、四段描述含真實範例、strict schema(上下限 / default / describe)、READ_ONLY - [ ] 找不到 / 缺參數 → 文字結果含下一步;無 throw - [ ] 輸出經 `truncateIfNeeded`;列表有 `limit` - [ ] 讀檔只從列舉結果比對;有路徑穿越測試 - [ ] stdio 與 HTTP 都能跑;`/health` 免驗證;設 token 後未帶 → 401 - [ ] 全部日誌 `console.error`;`grep -rn "console.log" src/` 無結果 - [ ] 測試 colocated、覆蓋率 ≥ 55%(新專案);第 4 節基準值未下降(既有專案) - [ ] stdio 冒煙輸出(tools/list 工具數、一次 tools/call)貼到 PR - [ ] README 環境變數表、檔頭工具清單、`servers/*.json` 描述已同步 - [ ] Dockerfile:多階段、prod-only deps、`TRANSPORT=http`、`EXPOSE <PORT>`;compose 有 healthcheck 與 TZ --- ## 6. 常見錯誤 | 症狀 | 原因 | 修正 | |---|---|---| | Claude Desktop 顯示 server 啟動後立刻斷線 / JSON 解析錯 | stdio 模式有東西寫到 stdout | 全改 `console.error` | | gateway 呼叫回 `Bad Request: no valid session ID provided` | 用戶端 / proxy 沒轉送 `mcp-session-id` header,或第一個請求不是 initialize | nginx 保留 header;確認 initialize 先行 | | 能呼叫工具但收不到串流回應 | 反向代理 buffer SSE / 60s 斷線 | nginx `proxy_buffering off`、`proxy_read_timeout 3600s`(見 `elf-mcp-gateway`) | | `/mcp/<x>` 回 404 `Unknown corpus` | scope 不存在(大小寫或拼錯) | 用 `docs_list_corpora` 查 id;id 一律小寫 | | 改了 md 沒生效 | 手動加的快取沒做 mtime 失效 | 照 `readContent` 的 mtime 模式 | | 測試在 CI 綠、在本機紅(或反之) | 測試依賴打包語料且另一測試改了 `DOCS_CORPORA_DIR` 未還原 | `afterAll` 還原 env + `_clearCaches()` | | 模板測試檔無法被 vitest 轉譯 | JSDoc 註解內寫了 `src/**/*.test.ts`,`*/` 提前關閉註解 | 註解內避免 `*/` 字元序列 | | `npm run clean` 在 PowerShell 失敗 | `rm -rf` 是 bash 指令 | 用 `Remove-Item -Recurse -Force dist` 或改 `rimraf` | --- ## 7. 待確認 1. Node 版本統一:團隊標準 24.18;現況 Docker `node:26-alpine`(三個 server)、CI `node-version: 20`、`engines: >=18`、gateway runtime Node 22。以哪個為準、何時遷移。 2. 套件管理:團隊 pnpm 11.x(確切版號待 `elf-stack` 定案);本 repo 三個 server 都是 npm + `package-lock.json`。既有 server 是否遷移。 3. 測試放置:團隊標準 colocated `*.test.ts` + 55%;`docs-mcp-server` 用 `tests/` 且無覆蓋率門檻。是否搬移並加門檻。 4. .NET 10 MCP server:repo 內**沒有**任何 C# MCP server(`.cs` 檔都是 SqlSugar 範例 / 效能測試資料),本 skill 不提供 .NET 範本;若要用官方 C# SDK,需另立規範。 5. zod 3 vs 4、express 4 vs 5 在 repo 內分岔(證據 E3)——統一到哪一版。 6. `express-rate-limit`(`^8.5.2`)加在 `mcp/docs-mcp-server/package.json` 但**從未 commit**(`git show HEAD:mcp/docs-mcp-server/package.json | grep -c express-rate-limit` → 0,實測 2026-10-04),`src/` 也無任何 import;nginx 範例說 rate limit 放邊界。應用層要不要限流;不限流就該把這行從工作目錄拿掉,別讓它一直掛著。 7. Bearer token 比對用 `===`(非 constant-time);是否改 `crypto.timingSafeEqual`。 8. 工具層逾時:現有工具都是同步檔案讀取、沒有 per-tool timeout;若新 server 會呼叫外部 API,逾時值與重試策略待定(gateway 端 `MCP_SERVER_INIT_REQ_TIMEOUT_SEC=30` 只管初始化)。 9. 工具描述語言:現況繁中描述+英文工具名;給 gateway 後多語系用戶是否要中英並列。
GitHub에서 보기