生活分享

Claude Code|建立自己的唯讀 MCP 工具

讓 Claude 查詢本機練習待辦資料。本篇讓 Claude 讀取你自己提供的待辦資料。你會建立唯讀 MCP server,先用測試客戶端驗證工具清單與呼叫結果,再連進 Claude Code。完成不是畫面顯示 Connected,而是工具真的回傳指定資料、參數錯誤可以辨認,而且伺服器沒有修改原始檔案。

閱讀時間約 7 分鐘

建立自己的唯讀 MCP 工具:文件、螢幕與完成記號的幾何插圖
圖片:Mokaair (© Mokaair)
本篇目錄
  1. 先看資料與工具契約
  2. 建立唯讀 server
  3. 把業務邏輯獨立出來
  4. 用真正的 MCP 客戶端確認
  5. 接到 Claude Code
  6. 故障練習與定位順序
  7. 確認連線成功之後還要確認資料
  8. 完成與下一步

本篇讓 Claude 讀取你自己提供的待辦資料。你會建立唯讀 server,先用測試客戶端驗證工具清單與呼叫結果,再連進 Claude Code。完成不是畫面顯示 Connected,而是工具真的回傳指定資料、錯誤可以辨認,而且伺服器沒有修改原始檔案。

先讀 及。下載第 79 篇材料,切到 starter。需要 Node.js 22;Claude 實機步驟另需有效登入。閱讀約 20 分鐘、實作約 45 分鐘。

先看資料與工具契約

先看資料與工具契約 → 建立唯讀 server → 把業務邏輯獨立出來
先看資料與工具契約 → 建立唯讀 server → 把業務邏輯獨立出來 · 圖片:Mokaair (© Mokaair)
閱讀完整文字說明

建立自己的唯讀 MCP 工具,以流程和文件圖形呈現教學重點。

本次假資料在 fixtures/tasks.json,共三筆待辦,其中兩筆同名但 id 不同。伺服器提供 list_tasks 與 get_task:前者是分頁清單,後者按 id 查詢單筆。將兩種操作分開,有助於讓呼叫者知道什麼時候該傳 offset,什麼時候只需要一個識別碼。

資料檔刻意沒有登入資料、私人地址或正式服務連線。即使你接錯測試命令,也不會因此查到正式產品資料。等整條鏈都理解後,再把資料來源換成自己的受控服務;那會需要重新設計認證、授權與資料範圍,不只是替換一個網址。

專案終端機:安裝已固定版本的練習依賴 · text
npm ci
npm run mcp:probe

材料鎖定官方 MCP v2 套件及 Zod 版本,lockfile 保存完整相依關係。不要把舊文章的 @modelcontextprotocol/sdk 匯入路徑與這一版的 @modelcontextprotocol/server 混在同一支程式。遇到找不到匯出時,先看 package.json 與實際安裝版本,再決定該讀哪一代文件。

建立唯讀 server

打開 mcp/server.mjs。McpServer 負責工具登記與協定處理,StdioServerTransport 則透過程序的標準輸入輸出交換訊息。這種連接方式由客戶端啟動本機子程序,不需要公開 HTTP 網址,也不表示資料已自動上傳到第三方資料庫。

mcp/server.mjs:最小工具的核心寫法 · javascript
import { McpServer } from '@modelcontextprotocol/server';
import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
import { z } from 'zod';
import { listTasks } from './data.mjs';

const server = new McpServer({ name: 'mokaair-todo-lab', version: '1.0.0' });
server.registerTool('list_tasks', {
  description: '唯讀查詢練習待辦;nextOffset=null 表示結束。',
  inputSchema: z.object({
    offset: z.number().int().min(0).default(0),
    limit: z.number().int().min(1).max(20).default(2)
  }),
  annotations: { readOnlyHint: true }
}, async args => ({
  content: [{ type: 'text', text: JSON.stringify(listTasks(args)) }]
}));
await server.connect(new StdioServerTransport());

這段核心範例只登記清單工具;下載材料的完整版本另有 completed 篩選及 get_task,測試以完整版本為準。若你想從零重打程式,先讓單一工具通過,再把第二個工具加回去,不要同時修改資料格式、傳輸方式與工具名稱。

readOnlyHint 是描述工具用途的提示,真正的唯讀特性仍取決於程式。本範例沒有寫入資料檔的程式路徑,也沒有呼叫外部修改 API。若在工具內加入檔案寫入,光是保留這個提示不會自動把寫入禁止。

把業務邏輯獨立出來

資料邏輯放在 mcp/data.mjs,讓分頁規則能在沒有 Claude 的情況下驗證。offset 是篩選後清單的位置,limit 介於一到二十;下一頁位置超出資料時回傳 null。呼叫端不能只看 items 長度猜測還有沒有下一頁,因為最後一頁可能剛好滿額。

呼叫 list_tasks({limit:2}) 的預期資料 · json
{
  "items": [
    { "id": "a", "title": "買牛奶", "completed": false },
    { "id": "b", "title": "整理桌面", "completed": true }
  ],
  "nextOffset": 2,
  "total": 3
}

接著以 offset=2 讀下一頁,應只剩 id=c,nextOffset 為 null。get_task 查不到資料時回傳 found=false;這與整個工具呼叫失敗不同。使用明確的結果結構,比回傳一句無法區分錯誤原因的「沒有資料」更容易讓後續程式處理。

用真正的 MCP 客戶端確認

mcp/probe.mjs 使用官方 Client 和 StdioClientTransport,會啟動 server、列出工具,再呼叫 list_tasks。這一步已走過序列化與協定交換,強度高於只在程式中直接呼叫 listTasks 函式。看到 tools 內包含兩個名稱,還要讀 result 的實際內容是否與 fixture 一致。

專案終端機:執行協定與邊界測試 · text
node --test tests/mcp.test.mjs

測試會驗證工具名稱、兩頁資料串接、未知 id,以及 limit=0 的錯誤結果。工具回傳 isError 時,外層協定連線仍可能正常;這代表應用層拒絕了輸入,不能把它判成網路已斷線。測試最後關閉客戶端,讓被啟動的程序正常結束。

如果你直接執行 node mcp/server.mjs 後畫面停住,這通常是伺服器正在等協定輸入。不要在 stdout 加一行「啟動成功」來排錯,因為 stdout 是 MCP 訊息通道。診斷可寫到 stderr,或由 probe 捕捉需要的結果;使用 Ctrl+C 結束你手動開啟的測試程序。

接到 Claude Code

在同一個 starter 目錄執行下面的命令。設定檔裡的 node 命令及 mcp/server.mjs 是給子程序使用,因此工作目錄必須正確。若要從別處啟動,改用你電腦上已核對的絕對路徑,不要照抄作者的家目錄。

專案終端機:只載入本練習 MCP 設定 · text
claude --strict-mcp-config --mcp-config mcp/fixtures.mcp.json
Claude Code 對話框:完成一次真實呼叫 · text
請使用 todo-lab 的 list_tasks 工具,先以 limit=2 讀第一頁,
再依 nextOffset 讀下一頁。列出三個 id、完成狀態與總筆數。
不要修改資料,也不要讀取其他專案。

請查看工具實際被呼叫的紀錄,而不是只看最終文字剛好列出 a、b、c。模型有可能從先前對話知道答案,或在你直接貼了資料後自行整理;那都不能證明 MCP 連線成功。必要時在下一次測試前修改一筆假資料,再建立新的工作階段核對。

故障練習與定位順序

先將設定裡的 server 路徑故意改錯,啟動應出現程序或連線錯誤;修復路徑後重跑 probe。第二個案例傳 limit=0,應得到輸入不合法,但同一客戶端之後仍能傳合法參數。第三個案例查不存在的 id,應得到 found=false,沒有把服務錯誤偽裝成找不到資料。

症狀優先檢查可用證據
程序起不來node、工作目錄、安裝版本子程序 stderr
有工具但資料不對fixture 與分頁參數原始工具回傳
Claude 沒呼叫需求與工具描述是否明確工具使用紀錄
參數被拒絕Schema 型別及範圍isError 與錯誤文字

確認連線成功之後還要確認資料

工具清單能證明伺服器有回報工具名稱,無法單獨證明查詢結果符合契約。至少完成一次實際呼叫,檢查回傳的 a、b 兩筆資料、分頁位置與總數。若工具名稱正確但資料格式不同,先修正伺服器與用戶端對契約的理解。

關閉測試用戶端後確認子程序已退出。若每次重試都留下背景伺服器,後續錯誤可能來自舊程序或重複啟動,容易被誤認為 MCP 連線不穩。

完成與下一步

最後記錄 Node、SDK、Claude 版本,保留 probe 結果、測試輸出與一次真實工具呼叫。用檔案雜湊或 Git diff 確認 fixture 沒有被修改。結束對話後確認沒有遺留由你手動啟動的 server;不要為了清理而終止所有 node 程序。

本篇的完成分兩層:本機協定測試通過,以及 Claude 確實使用工具。若後者因登入過期尚未完成,就保留為待測;正常的 probe 結果不會自動解決 Claude 的認證問題。這個分層也適用於日後連接真正服務。

小練習是新增 completed=false 的查詢,確認總筆數及下一頁都根據篩選結果計算。接續可以擴充測試;需要遠端服務時,再閱讀,不要直接把本機 stdio 範例當成可公開的 HTTP 服務。

回總目錄

  • 生活分享

    Claude Code|建立第一個 mod:在 Claude Code 行程內數工具呼叫

    寫一個三檔案的 mod,用驗證器與測試確認它掛上的事件。文件把 mod 定義成多了入口檔的 plugin:入口檔叫 hooks module,Claude Code 在事件發生時呼叫裡面的函式,函式可以觀察、改寫或接手事件。

  • 生活分享

    Claude Code|Git Worktree 平行工作

    隔離多個任務的檔案與分支。Git Worktree 讓同一儲存庫擁有多個工作目錄,各自使用分支與檔案。本篇會把待辦篩選與文件整理分開,確認兩個 session 不會直接改到彼此的檔案,再把其中一個成果整合回主分支。你也會知道何時可以安全清理工作目錄。

  • 生活分享

    Claude Code|雙 Worktree 實作與衝突整合

    隔離兩項功能,最後完成整合與回歸。兩個 Claude 工作階段同時編輯專案,最容易出現的問題是互相改到同一份檔案,或各自測試通過、整合後卻失敗。本篇用兩個 Worktree 分別處理篩選預設值與介面文字,故意製造一次小衝突,再完成整合、驗證與清理。你不需要先啟用 Agent Teams。

  • 生活分享

    Claude Code|比較流程品質、用量與執行時間

    以同一資料集比較兩種工作方法。比較兩種 Claude 工作方法時,不能只挑成功那一次,也不能只看第一個答案有多快。本篇用固定案例、原始紀錄和一致判準,比較品質、重試、等待與人工整合時間,最後寫出有樣本數與限制的報告,而不是保證某個方法一定省錢。

最新旅遊情報攻略

資料來源

生活分享