MCP 伺服器

OCR 作為 MCP client,把外部 server 的工具併入評審 agent

是什麼

OCR 可以作為 Model Context Protocol(MCP)client。你把它指向一個或多個外部 MCP server,這些 server 暴露的工具就會提供給評審 agent——與 file_read、code_search 等內建工具並列。

何時使用

當評審器需要 diff 之外的脈絡時,就該引入 MCP server:

如果只需要讀 repo 本身,內建工具就夠了——MCP 是為了觸達 checkout 之外的東西。

配置

# 最小配置:只給命令
ocr config set mcp_servers.docs.command npx

# 參數
ocr config set mcp_servers.docs.args '["-y", "@acme/docs-mcp-server"]'

# 限制暴露給評審器的工具
ocr config set mcp_servers.docs.tools '["search_docs", "get_page"]'

# server 啟動前跑的 setup 命令
ocr config set mcp_servers.docs.setup "npm install -g @acme/docs-mcp-server"

# 環境變數(KEY=VALUE 條目)
ocr config set mcp_servers.docs.env '["DOCS_TOKEN=secret", "DOCS_REGION=eu"]'

# 移除
ocr config unset mcp_servers.docs

欄位

欄位型別必填說明
commandstring✓啟動 server 的可執行檔(npx、uvx、絕對路徑)
argsstring[]傳給 command 的參數
toolsstring[]工具名白名單;空 = 註冊全部
setupstringserver 啟動前跑一次的 shell 命令(5 分鐘超時)
envstring[]額外環境變數(KEY=VALUE)

工具過濾

預設註冊 server 宣告的每個工具。當工具太多時用 tools 白名單——更少、更精準的工具讓 agent 更專注、降低 token 成本。白名單裡 server 沒有的名字會被跳過並警告(拼寫錯誤會顯示在 stderr,而不是默默無聲)。

名稱衝突

MCP 工具名與內建工具共享同一命名空間。若與內建/保留工具(file_read、code_search…)或其他 server 的工具衝突,OCR 會跳過並記錄警告。先註冊者勝出。

排錯

所有 MCP 診斷都輸出到 stderr([ocr] 前綴),絕不污染 stdout 的 JSON 輸出:

📖 教學解說:MCP 伺服器深入

OCR 是 MCP client 而非 server

這是最常被誤解的:OCR 消費 MCP server 暴露的工具,而不是暴露自己的工具給別人。你把外部 MCP server 指向 OCR,它的工具就成為評審 Agent 的一部分——與 file_read、code_search 並列。

何時該引入 MCP

判斷標準:評審需要 diff 之外的脈絡嗎?

  • 需要看 Jira issue → MCP server 查 issue
  • 需要查內部 API 文件 → MCP server 查文件
  • 需要跑 linter → MCP server 暴露 linter 工具

如果只需要讀 repo 本身,內建工具就夠了。

工具白名單的實務價值

當 MCP server 暴露 20+ 工具時,Agent 會在每次請求中都看到所有工具定義——消耗 token。用 tools 白名單只暴露你真正需要的 2-3 個,Agent 更專注、token 更省。拼寫錯誤會在 stderr 顯示警告而不是默默無聲。

🔍 Worked Example:配置 MCP server 供評審使用

Step 1 — 設定 MCP server 命令

ocr config set mcp_servers.docs.command npx

Step 2 — 設定參數

ocr config set mcp_servers.docs.args '["-y", "@acme/docs-mcp-server"]'

Step 3 — 設定工具白名單

ocr config set mcp_servers.docs.tools '["search_docs", "get_page"]'

Step 4 — 設定環境變數

ocr config set mcp_servers.docs.env '["DOCS_TOKEN=secret"]'

Step 5 — 跑評審(MCP 工具自動可用)

ocr review

預期產出

✓ MCP server "docs" started
✓ Tools registered: search_docs, get_page
✓ Reviewing 4 files...
  (agent used search_docs to verify API usage)

常見錯誤與診斷

錯誤訊息診斷修復
failed to start MCP server30 秒初始化超時或 command 不在 PATH檢查 setup 命令、確認 command 可執行
tool conflicts with built-in, skippingMCP 工具名與內建衝突改名或從 tools 白名單去掉
allowed tool not found in servertools 白名單拼寫錯誤檢查拼寫,stderr 有警告

練習 / 驗收清單

  • 能解釋 OCR 是 MCP client 而非 server
  • 能判斷何時該引入 MCP server
  • 能配置 tools 白名單
  • 能解釋名稱衝突的「先註冊者勝出」規則
看完這頁你應該能說出:OCR 是 MCP client(不是 server)、何時該引入 MCP、tools 白名單與 setup 的用途、以及名稱衝突的「先註冊者勝出」規則。

延伸閱讀:6 個內建工具 · 設定 · 程式碼:internal/mcp

① 進階真實情境 Worked Example:把內部 linter 包成 MCP server,讓評審 agent 直接呼叫

情境

你們公司有套內部的 acme-lint(檢查公司特有的 API 使用規範,例如「不得直接呼叫 legacy payment 端點」)。你想讓 OCR 的評審 agent 在 Main 迴圈裡直接跑 acme-lint 拿結果當脈絡,而不是靠模型瞎猜。

Step 1 — 寫一個極簡 MCP server(stdio + tools/list + tools/call)

# acme-lint-mcp/server.js
const { Server } = require('@modelcontextprotocol/sdk/server/index.js')
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js')

const server = new Server({ name: 'acme-lint', version: '1.0.0' })
server.setRequestHandler(server.listTools, async () => ({
  tools: [{
    name: 'run_acme_lint',
    description: '對指定檔案跑 acme-lint,回傳違規清單',
    inputSchema: { type: 'object', properties: { path: { type: 'string' } } }
  }]
}))
server.setRequestHandler(server.callTool, async (req) => {
  const { path } = req.params.arguments
  const out = execSync(`acme-lint --json ${path}`)
  return { content: [{ type: 'text', text: out.toString() }] }
})
server.connect(new StdioServerTransport())

Step 2 — 註冊進 OCR 並只暴露這個工具

ocr config set mcp_servers.acmelint.command     node
ocr config set mcp_servers.acmelint.args        '["/path/to/acme-lint-mcp/server.js"]'
ocr config set mcp_servers.acmelint.tools       '["run_acme_lint"]'

Step 3 — 跑評審,觀察 agent 是否用了它

ocr review --format json --audience agent
# session JSONL 中可看到 tool_call: run_acme_lint

Step 4 — 驗證工具真的被當脈絡(看 viewer)

開 viewer,找 main_task 泳道,看 tool call 的結果有沒有影響模型的 code_comment(例如「此檔違反 acme-lint 的 legacy payment 規則」)。

為什麼選這條審查路徑

公司特有規範(legacy 端點禁用的黑白名單)是模型不可能憑常識知道的——不給它工具,它就只能「猜」。把 acme-lint 包成 MCP 工具,讓 Agent 在需要時以確定性工具取得確定性事實,評論就從「猜測」升級成「引用公司規則」。這正是 MCP 頁開頭說的「評審需要 diff 之外的脈絡」的最佳實例。

預期產出

✓ MCP server "acmelint" started
✓ Tools registered: run_acme_lint
Reviewing 4 files...
  agent called run_acme_lint on payment/gateway.go
  comment: "此檔呼叫 legacy payment 端點,違反公司規範 (acme-lint rule LP-001)"

② 深入原理擴充:MCP 工具的生命週期與「工具結果被誤用」

MCP 工具的三階段

  • 初始化——OCR 啟動 server(setup 若有)+ 30 秒超時等它回應。
  • tools/list——OCR 取得工具清單,套用 tools 白名單,檢查名稱衝突(先註冊者勝出)。
  • tools/call——Agent 在 Main 迴圈依需呼叫;結果作為 tool_call 事件寫進 session JSONL。

工具結果對 Agent 而言只是「一段文字脈絡」——它不會自動成為評論。就像 file_read 發現的問題「按設計被忽略」,MCP 工具結果也只在「目前檔 diff 可觀察」時才會進入評論。

「看起來整合成功但其實有隱患」的案例

# acme-lint server 對不存在/失敗的檔案回傳:
{"content":[{"type":"text","text":"[]"}]}    # 空清單 = "沒違規"
# 但其實是路徑不存在,acme-lint 失敗被 server 吞掉、回傳空
# Agent 收到 "[]" → 解讀成「此檔乾淨」→ 評論零條

工具本身沒被設計成「回報錯誤」——失敗時回傳空陣列,Agent 無法分辨「乾淨」與「執行失敗」。隱患:看似完整整合,實際上 linter 在關鍵檔上根本沒跑。防禦:server 對失敗應回傳錯誤內容(isError: true 或明確錯誤文字),讓 Agent 知道「工具失敗,不是乾淨」;並在 viewer 的 tool call 結果檢查是否有「被吞掉的錯誤」。

③ 診斷式疑難排解

症狀可能原因解決方案
failed to start MCP server30 秒初始化超時、command 不在 PATH、setup 失敗檢查 stderr 的 [ocr] 診斷;先手動跑 node server.js 確認可啟動
工具被註冊但 agent 從沒呼叫Agent 不知道何時該用;或工具名描述不清改 server 的 description,明確寫「當發現 legacy payment 呼叫時」
工具名與內建衝突被跳過「先註冊者勝出」改名或從 tools 白名單去掉衝突者
工具回傳「乾淨」但其實失敗server 吞掉錯誤回傳空讓 server 回傳 isError 或明確錯誤文字
工具結果沒進評論脈絡工具發現的問題按設計被忽略確認問題在目前檔 diff 可觀察;這是設計而非 bug

④ 進階挑戰題

  1. 挑戰一:寫一個 MCP server 的 callTool handler,讓它對「檔案不存在」回傳 isError: true 而非空清單。
  2. 挑戰二:你的 MCP server 暴露 15 個工具。為什麼「只暴露 2-3 個」更省 token?工具定義在每次請求都會被重複送給模型。
  3. 挑戰三:判斷題——「把 code_search 包成 MCP 工具」有意義嗎?為什麼(提示:內建已存在,衝突規則)?