跳至主要內容
模組 3:真實架構 1 / 6
進階 Session 13 Control Protocol JSON RPC

控制協議

理解 CLI 和 SDK 之間的雙向 JSON RPC。

2026年3月20日 20 分鐘閱讀

你將學到什麼

Claude Code 不是單一程序。它是兩個程序透過定義明確的協議進行協作。理解這種分離 — 以及在兩端之間流動的訊息 — 讓你能夠建構自訂前端、自動化工作流程,並除錯非預期行為。

完成後,你將了解:

  • 為什麼 Claude Code 被分為 CLI 和 SDK 程序
  • 雙向 JSON-RPC 訊息如何透過 stdin/stdout 傳遞
  • 三種訊息類別:請求、回應和通知
  • 串流在協議層面如何運作
  • 權限提示如何在協議中傳遞
  • 如何追蹤一則使用者訊息的完整往返過程

問題是什麼

大多數 CLI 工具是單體式的:一個程序讀取輸入、執行工作、印出輸出。但 Claude Code 有兩個截然不同的任務:

  1. 使用者介面 — 渲染終端 UI、處理鍵盤輸入、顯示串流文字
  2. AI 引擎 — 管理 API 呼叫、執行工具、強制權限、追蹤上下文

將兩者綁在一個程序中會造成緊密耦合。想把 Claude Code 嵌入 VS Code?你需要重寫 UI 層。想用腳本驅動它?你需要模擬終端輸入。想在遠端伺服器上執行引擎?不可能。

解決方案是乾淨的分離:

┌──────────────────┐         ┌──────────────────┐
│       CLI        │  stdin   │       SDK        │
│                  │ ──────► │                  │
│  Terminal UI     │         │  API Client      │
│  User input      │  stdout  │  Tool Executor   │
│  Permission UI   │ ◄────── │  Context Manager  │
│  Display         │         │  Permission Logic │
└──────────────────┘         └──────────────────┘

CLI 處理使用者看到的一切。SDK 處理 AI 做的一切。它們透過一個控制協議溝通:經由 stdin 和 stdout 傳遞的雙向 JSON-RPC 串流。

如何運作

雙程序架構

當你啟動 claude 時,它會啟動兩個程序:

Terminal


┌──────────────────────────────────────────────┐
│  CLI Process (Node.js)                       │
│                                              │
│  - Renders terminal UI (Ink/React)           │
│  - Captures user keystrokes                  │
│  - Displays streaming assistant text          │
│  - Shows permission prompts                  │
│  - Manages session history display           │
│                                              │
│         stdin (pipe)  ▼  ▲  stdout (pipe)    │
│                                              │
│  ┌────────────────────────────────────────┐  │
│  │  SDK Process (Node.js, child process)  │  │
│  │                                        │  │
│  │  - Sends requests to Claude API        │  │
│  │  - Parses streaming API responses      │  │
│  │  - Executes tool calls (Read, Edit,    │  │
│  │    Bash, etc.)                         │  │
│  │  - Manages message history             │  │
│  │  - Resolves permissions                │  │
│  │  - Handles context compaction          │  │
│  └────────────────────────────────────────┘  │
└──────────────────────────────────────────────┘

SDK 作為 CLI 的子程序運行。它們透過子程序的 stdin 和 stdout 管道通訊 — 沒有網路 socket、沒有 HTTP,只有以換行符分隔的原始 JSON 訊息管道。

訊息類型

此協議使用三種訊息類別,靈感來自 JSON-RPC 2.0:

1. 請求 — 預期會有回應。帶有 id 欄位。

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "user_message",
  "params": {
    "content": "Fix the bug in auth.ts",
    "session_id": "abc-123"
  }
}

2. 回應 — 回答請求。帶有匹配的 id

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "status": "completed",
    "usage": { "input_tokens": 1200, "output_tokens": 350 }
  }
}

3. 通知 — 發送後即忘。沒有 id 欄位。

{
  "jsonrpc": "2.0",
  "method": "assistant_token",
  "params": {
    "token": "Let me ",
    "message_id": "msg-456"
  }
}

區分這些很重要:請求建立一個對話(一方等待回應),而通知是單向廣播。

串流模型

當 SDK 收到 API 回應時,它不會等待完整回應。每個 token 都作為通知串流出去:

SDK → CLI:  {"method": "assistant_token", "params": {"token": "Let "}}
SDK → CLI:  {"method": "assistant_token", "params": {"token": "me "}}
SDK → CLI:  {"method": "assistant_token", "params": {"token": "check "}}
SDK → CLI:  {"method": "assistant_token", "params": {"token": "the "}}
SDK → CLI:  {"method": "assistant_token", "params": {"token": "file."}}
SDK → CLI:  {"method": "tool_use_start", "params": {"name": "Read", ...}}

CLI 立即渲染每個 token,產生你在終端中看到的打字機效果。這類似於 Server-Sent Events (SSE) — 一串小訊息朝單一方向流動。

Claude API (SSE)        SDK Process          CLI Process
     │                       │                    │
     │── token: "Let " ────►│                    │
     │                       │── notification ──►│── render "Let "
     │── token: "me " ─────►│                    │
     │                       │── notification ──►│── render "me "
     │── tool_use ─────────►│                    │
     │                       │── tool_start ────►│── show tool badge
     │                       │                    │
     │                       │── (execute tool) ──│
     │                       │                    │
     │                       │── tool_result ───►│── show result

權限流程

當 SDK 遇到需要使用者核准的工具呼叫時,一個請求-回應交換會處理它:

SDK → CLI:  {
  "jsonrpc": "2.0",
  "id": 42,
  "method": "permission_request",
  "params": {
    "tool": "Bash",
    "command": "npm install express",
    "risk_level": "medium"
  }
}

(CLI shows permission prompt to user)
(User presses 'y')

CLI → SDK:  {
  "jsonrpc": "2.0",
  "id": 42,
  "result": {
    "decision": "allow",
    "scope": "session"
  }
}

SDK 在此請求上阻塞。在 CLI 發回回應之前,它不會執行該工具。這就是權限系統的運作方式 — SDK 是決定什麼需要權限的權威,而 CLI 是決定是否授予權限的權威。

SDK                           CLI
 │                             │
 │── permission_request ─────►│
 │         (blocked)           │── Show prompt
 │         (waiting)           │── User decides
 │◄── permission_response ────│
 │                             │
 │── execute tool              │
 │── tool_result ────────────►│── Display result

協議版本控制

交換的第一則訊息始終是能力協商:

CLI → SDK:  {
  "jsonrpc": "2.0",
  "id": 0,
  "method": "initialize",
  "params": {
    "protocol_version": "1.0",
    "capabilities": {
      "streaming": true,
      "permissions": true,
      "progress": true
    }
  }
}

SDK → CLI:  {
  "jsonrpc": "2.0",
  "id": 0,
  "result": {
    "protocol_version": "1.0",
    "capabilities": {
      "tools": ["Read", "Edit", "Bash", "Glob", "Grep", ...],
      "mcp_servers": 3
    }
  }
}

這個握手確保雙方就支援哪些功能達成共識。如果 CLI 是較舊的版本,SDK 可以停用較新的功能。如果 SDK 引入了新的通知類型,較舊的 CLI 可以安全地忽略它。

關鍵洞見

控制協議是讓 Claude Code 既是 CLI 工具又是 SDK 的關鍵。 驅動終端 UI 的同一引擎可以被 VS Code、Web 介面或 Python 腳本驅動。協議就是邊界。

這有深遠的影響:

  1. 自訂前端。 建構一個能說協議語言的 GUI,你就能獲得 Claude Code 的全部能力而不需要終端。
  2. 程式化自動化。 @anthropic-ai/claude-code npm 套件直接暴露 SDK — 你的程式碼變成 CLI,發送訊息並處理回應。
  3. 遠端執行。 協議與傳輸方式無關。今天它透過 stdin/stdout 管道運行。只需最少的修改就能透過 WebSocket 或 TCP 運行。
  4. 測試。 你可以透過發送腳本化的協議訊息來測試 SDK,完全不需要任何 UI。

協議不是實作細節 — 它就是架構本身。

實作範例

追蹤訊息的完整堆疊路徑

讓我們追蹤當你在 Claude Code 中輸入「What’s in README.md?」時會發生什麼:

Step 1: CLI captures input
─────────────────────────
User types: "What's in README.md?"
CLI packages it into a JSON-RPC request.

CLI → SDK:
  { "id": 5, "method": "user_message",
    "params": { "content": "What's in README.md?" } }


Step 2: SDK calls Claude API
─────────────────────────────
SDK builds the full messages array (system prompt + history + new message)
SDK sends to Claude API via streaming HTTP.


Step 3: API streams back tokens + tool call
───────────────────────────────────────────
API response includes text and a tool_use block:
  "Let me read that file."
  tool_use: Read { file_path: "README.md" }

SDK forwards each token as a notification:
SDK → CLI:  { "method": "assistant_token", "params": { "token": "Let " } }
SDK → CLI:  { "method": "assistant_token", "params": { "token": "me " } }
...
SDK → CLI:  { "method": "tool_use_start",
              "params": { "name": "Read", "input": { "file_path": "README.md" } } }


Step 4: SDK checks permissions
──────────────────────────────
Read tool for README.md → allowed by default (no prompt needed).
SDK executes the tool directly.


Step 5: SDK feeds result back to API
─────────────────────────────────────
SDK appends tool_result to messages array.
SDK calls Claude API again with the updated context.


Step 6: API streams final response
───────────────────────────────────
API → SDK → CLI (token by token):
  "The README.md contains a project description for..."

SDK → CLI:  { "method": "turn_complete",
              "params": { "stop_reason": "end_turn" } }


Step 7: SDK sends response to original request
───────────────────────────────────────────────
SDK → CLI:
  { "id": 5, "result": { "status": "completed",
    "usage": { "input_tokens": 2400, "output_tokens": 180 } } }

以程式方式使用 SDK

因為協議是定義明確的,你可以將 Claude Code 作為函式庫使用:

import { claude } from "@anthropic-ai/claude-code";

// The SDK speaks the same protocol — your code is the "CLI"
const result = await claude("What's in README.md?", {
  cwd: "/my/project",
  allowedTools: ["Read", "Glob", "Grep"],
});

console.log(result.text);
// "The README.md contains..."

在底層,這會啟動 SDK 程序並透過 stdin/stdout 交換 JSON-RPC 訊息 — 與終端 CLI 的做法完全相同。

前後對比

單體式 CLI雙程序協議
UI 和引擎緊密耦合關注點清楚分離
只有一個前端(終端)任何能說 JSON-RPC 的前端
難以自動化程式化 SDK 使用內建支援
難以測試協議訊息可以腳本化
無法遠端執行引擎傳輸方式無關的設計
權限邏輯與 UI 混合SDK 決定什麼需要權限,CLI 決定是否授予

下一堂課

你現在理解了 CLI 和 SDK 如何通訊。但工具不是擴展 Claude Code 的唯一方式。第 14 堂課介紹 MCP 整合 — Model Context Protocol 如何讓你透過標準協議接入無限的外部工具,使 Claude Code 具備無限的可擴展性。