把同一個工具同時做成 CLI 和 MCP 之後, 我學到的事
這篇記錄 content-i18n 同時支援 CLI 和 MCP server 時的 system design
重點不是 MCP protocol 本身, 而是同一個 workflow 要怎麼被兩種 entrypoint 共用
核心問題:
- CLI 是什麼
- MCP 是什麼
- 它們各自應該負責什麼
- 為什麼不能各自長一套 workflow
content-i18n最後怎麼切 core, CLI, MCP
什麼是 CLI
CLI 是 command-line interface, 也就是人透過 terminal 下 command 操作程式
例如:
|
|
這個 command 裡有幾個部分:
content-i18n: 要執行的程式review: 要執行的 action--config: config file path--file: target file--source: source file
CLI 的使用者通常是人, shell script, CI job, 或其他 automation
一個好的 CLI 通常要處理:
- command name 清楚
- flag 好理解
- error message 看得懂
- exit code 可以給 script 判斷
- output 可以給人讀, 也可以給 shell 接
CLI 是 human-facing entrypoint
CLI 怎麼運作
CLI 大概是這條 flow:
|
|
以 content-i18n review 來說:
- 讀 command 和 flags
- 載入
content-i18n.yaml - 找到 source 和 target file
- 呼叫真正的 review workflow
- 把結果印給使用者
- 根據結果回傳 exit code
CLI 可以決定 output 怎麼印, 錯誤訊息怎麼呈現, command 怎麼命名
但 review 檢查什麼, queue state 怎麼算, sync-status 什麼時候能更新, 不應該放在 CLI layer
CLI layer 要怎麼設計
CLI layer 適合負責:
- command tree
- flag parsing
- config path loading
- shell-friendly output
- human-readable error
- exit code
CLI layer 不適合負責:
- prepare 的 business rule
- review 的 business rule
- queue state 推導
- sync-status 判斷
- batch orchestration
- source / target mapping rule
CLI 是 adapter:
|
|
它把人的 command 轉成 core workflow 可以吃的 input, 再把 core workflow 的 result 轉成人看得懂的 output
什麼是 MCP
MCP 是 Model Context Protocol
在這個專案裡, MCP 讓 agent runtime 可以用 structured tool call 操作本機工具
如果 CLI 是 human 跟 local program 之間的 contract, MCP 就是 agent runtime 跟 local program 之間的 contract
CLI call:
|
|
MCP tool call:
CLI 是 command + flags
MCP 是 tool name + structured arguments
形式不同, 但最後要做的事情可以是同一件事
MCP 怎麼運作
MCP layer 大概是這條 flow:
|
|
一支 MCP tool 通常要定義:
- tool name
- description
- input schema
- handler
- response shape
以 content_i18n_review_translation 來說:
- 宣告這支 tool 存在
- 定義它需要哪些 arguments
- 接 agent runtime 傳進來的 request
- decode 成 core workflow 可以用的 input
- 呼叫 review workflow
- 回傳 structured response 給 agent
MCP 的 output 要讓 agent 可以接著做下一步
所以 response 通常要包含:
- issue list
- file path
- source path
- sync readiness
- next action hint
但 MCP 不該自己實作 review
MCP layer 要怎麼設計
MCP layer 適合負責:
- public tool contract
- argument schema
- request decoding
- handler registration
- structured response
- agent-friendly result shape
MCP layer 不適合負責:
- 自己決定 review rule
- 自己推導 queue state
- 自己判斷 completion
- 自己處理 source / target mapping
- 自己補 batch orchestration rule
content-i18n 的 MCP server 後來整理成:
|
|
這個結構讓責任比較清楚:
- definition 放 tool contract
- handler 接 request 並呼叫 core
- registration 負責把 tool 掛到 server
- response 負責 agent-facing output shape
registration 也改成 registry / spec pattern
這樣 public contract 不會藏在一大段手工註冊裡, 新增或移除 tool 時也比較不容易漏掉
為什麼 CLI 和 MCP 要共用同一個 core workflow
同一個工具支援 CLI 和 MCP 時, 最大風險是兩邊慢慢變成兩個產品
如果兩邊各自實作邏輯, 很快就會 drift:
- CLI review pass, MCP review fail
- MCP sync 成功, CLI queue 還是 stale
- batch path 和 single-file path 檢查不一致
- agent 繞過 review, 直接改 target file
- completion rule 在不同入口有不同解釋
所以 workflow 必須只有一個 authority
在 content-i18n 裡, 這個 authority 是 internal/core
|
|
CLI 和 MCP 可以有不同 input / output
但 prepare, review, sync, queue, batch 的 meaning 只能有一份
content-i18n 的 core boundary
最後比較穩定的分層:
|
|
責任切分:
| Layer | Responsibility |
|---|---|
| CLI | command parsing, flags, config path, exit code, human-readable output |
| MCP | tool definition, request decoding, handler registration, structured response |
| internal/core | prepare, review, sync, queue, batch, validation workflow |
| validator / structure / frontmatter / content | low-level document rules |
| providers | DeepL, Google, ai-harness provider boundary |
CLI 和 MCP 呼叫 internal/core
internal/core 不需要知道 caller 是 CLI 還是 MCP
Review rule 改一次, CLI 和 MCP 都會吃到同一個行為
MCP tool surface 要怎麼設計
MCP tool surface 不能太碎
早期如果把 tool 拆成 low-level primitive, 看起來很有彈性:
- read source
- create work packet
- validate translation
- write translation target
- next translation
- repair translation
但這對 agent 反而危險
因為 tool surface 不只是 API 清單, 它也在暗示 agent 應該怎麼做事
raw primitive 太多, agent 很容易自己組 workflow:
|
|
這就是 tool bypass
所以 content-i18n 後來把 public MCP surface 收斂成 workflow-level tools:
content_i18n_statuscontent_i18n_prepare_translationcontent_i18n_review_translationcontent_i18n_sync_statuscontent_i18n_translation_queuecontent_i18n_translate_batchcontent_i18n_validate_site
這 7 支 tool 比 low-level primitive 更適合 agent:
prepare_translation比read_source+create_work_packet更接近真實 workflow steptranslation_queue已經能帶出 candidate, 不需要額外next_translationreview_translation回 structured issue 和 sync readiness, 比單純 validator wrapper 更完整translate_batch保留 batch orchestration, 不讓 agent 自己拼 batch loopsync_status把 completion 變成正式狀態, 不只是檔案存在
比較好的 MCP tool design:
- public tool 少
- operation 高階
- response 完整
- caller 不容易自己補 workflow
Queue state 怎麼運作
Queue model 讓 translation workflow 從一次性任務變成可維護系統
content-i18n 用三種 state:
completedstalemissing
這些 state 從下面資料推導:
- source discovery
- expected target path
- source hash
- translation status
三種 state 的意思:
| State | Meaning |
|---|---|
missing |
source exists, expected target does not exist |
stale |
target exists, but source changed after last sync |
completed |
review / validation passed, target synced with source |
翻譯不是 once-and-done
今天完成的 target, 只要 source 改了, 明天就可能 stale
所以 queue 不只是列出待翻譯檔案, 它是在回答:
- 哪些 target 不存在
- 哪些 target 已經跟 source 不同步
- 哪些 target 真的完成
- 下一個該處理哪一篇
MCP tool 也要尊重 queue model
如果 agent 可以跳過 queue, review, sync, workflow authority 會失效
Failure modes 和最後規則
這個設計主要避免幾種 failure mode
CLI / MCP drift
CLI 和 MCP 如果各自實作 review, 很快就會不一致
修法:
- CLI 和 MCP 都只呼叫 core
- review rule 只放一份
- queue state 只從 core 推導
Wrapper logic leak
MCP wrapper 如果開始補 workflow rule, wrapper 會越來越厚
修法:
- MCP handler 只 decode request
- handler 呼叫 core
- handler format structured response
- 不在 handler 裡重新判斷 business rule
Low-level tool bypass
low-level tools 太多, agent 會自己組 workflow
修法:
- public surface 收斂成 workflow-level tools
- response 帶足夠資訊
- review 和 sync-status 成為 official path
Fuzzy completion
如果 completion 只是 target file exists, queue 會失真
修法:
- review / validation 要 pass
- source-language leftover 要清掉
- sync-status 要成功
Duplicated state logic
如果 queue state 在 CLI, MCP, batch 各算一次, 結果會不一致
修法:
- queue state 推導集中在 core
- caller 只使用 core result
最後規則:
- CLI 和 MCP 是 entrypoint, 不是兩個產品
internal/core是唯一 workflow owner- MCP tool 要偏 workflow-level
- Public surface 要少, response 要完整
- Queue state 必須由系統推導
- Completion 要有正式 path
- Tool design 要防 agent bypass
這樣 CLI 和 MCP 才能共用同一個工具核心, 不會慢慢長成兩套系統