> ## Documentation Index
> Fetch the complete documentation index at: https://docs.echophrase.com/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP / IPC 安全模型

> Echophrase 本機 IPC 伺服器的綁定、驗證、閘控與範圍限制方式

# MCP / IPC 安全模型

本頁說明支援 `transcribe` MCP 工具與 `echophrase transcribe` CLI 指令的
本機 IPC 伺服器的安全設計 - 也就是 Echophrase 讓 AI 代理與桌面應用程式
溝通的部分。本頁寫給希望自行檢驗設計，而非只聽我們說法的讀者。以下每項
說法都已對照目前的原始碼核實，並附上檔案與行號，方便您自行查證。

<Note>
  本頁描述 CLI/MCP 行程與正在執行的桌面應用程式之間的傳輸方式。其餘四個
  MCP 工具（`record`、`stop`、`status`、`transcode`）完全不會用到這個伺服器 -
  它們完全在 CLI 行程內對本機檔案運作。工具總覽請參閱 [MCP 伺服器](/zh-Hant/mcp)。
</Note>

## 僅綁定本機，永不對外

IPC 伺服器明確且僅綁定 `127.0.0.1`：

```rust theme={null}
let addr = format!("127.0.0.1:{IPC_PORT}");
let std_listener = std::net::TcpListener::bind(&addr)...
```

（`src-tauri/src/ipc/mod.rs:124-126`）。程式碼中沒有任何路徑會綁定
`0.0.0.0` 或其他介面 - 模組本身的文件註解也載明同樣的限制
（`src-tauri/src/ipc/server.rs:3`：「僅綁定 `127.0.0.1`（絕不使用
`0.0.0.0`）」）。

這具體代表什麼：這個 socket 只有在同一台機器、同一個作業系統使用者工作階段
下執行的行程才能連上（loopback 介面不會對外部主機在您的區網或網際網路上
公開，而典型的作業系統層級 socket 權限，也代表共用機器上的其他作業系統
使用者帳號無法連線）。除了「已經以您的身分在您機器上執行的軟體」之外，
沒有任何東西能連到 1424 埠。相較於綁定在可路由介面上的伺服器，這是實質上
更小的攻擊面：沒有連接埠轉發設定錯誤、沒有路由器 UPnP 漏洞、也沒有可能
意外暴露的雲端中繼資料端點。

## 持有者權杖：每次啟動全新產生，停止即失效

每個 `POST /v1/transcribe` 請求都必須帶有
`Authorization: Bearer <token>`。檢查採用常數時間比對，而非單純的 `==`，
目的正是避免比對過程出現計時側通道
（`src-tauri/src/ipc/server.rs:100-135`，`constant_time_eq`）。不需驗證的
`GET /v1/health` 路由只回報應用程式版本 - 從不透露任何關於權杖或使用者的
資訊，因此探測這個埠不會得到「應用程式正在執行」以外的任何情報
（`src-tauri/src/ipc/server.rs:15-16, 65-71`）。

權杖的生命週期：

* **每次啟動伺服器都全新產生。** `start()` 每次被呼叫時都會呼叫
  `write_fresh_token()`，從 `rand::thread_rng()` 取出 32 個隨機位元組並
  以十六進位編碼 - 不是安裝時產生一次，也不會跨重啟重複使用
  （`src-tauri/src/ipc/mod.rs:107-133, 176-194`）。
* **產生後立即以僅擁有者可讀寫的權限寫入**（Unix 上為 `0o600`）
  （`src-tauri/src/ipc/mod.rs:187-191`）。（目前 Windows 與 macOS 依賴的是
  每位使用者設定檔目錄的預設 ACL，而非此程式路徑中明確的等效 chmod
  呼叫 - 詳見下方「限制」一節。）
* **存放位置**與 CLI 讀取的相同每位使用者資料目錄 -
  `<平台資料目錄>/echophrase-cli/run/ipc-token`
  （`src-tauri/src/ipc/paths.rs:76-105`）。桌面應用程式與 CLI 各自獨立
  解析這個路徑，但刻意設計為完全一致，避免任一方依賴另一方的 crate。
* **伺服器停止時即移除**，而非放著不管：`stop()` 在返回前會呼叫
  `clear_token_best_effort()`（`src-tauri/src/ipc/mod.rs:161-166,
  200-207`），強制結束時也會執行同樣的清除。這一點值得特別強調：
  停止的伺服器是**無法驗證**的，而不只是無法連線。即使有東西快取了
  前一個工作階段的有效權杖，只要應用程式（或僅是開關）關閉，該權杖
  立刻失效，因為支撐它的權杖檔案已不存在，且下次 `start()` 會產生
  完全無關的新權杖。
* CLI 自己的讀取路徑，會把「找不到權杖檔案」與「無法連線」視為同一種
  情況，回報同一則友善訊息，而非原始的連線錯誤（`cli/src/ops.rs:316-322`）。

## 明確選擇加入，並在伺服器端強制執行

伺服器不會因為應用程式啟動就自動開始監聽。`spawn()` 依序檢查兩個條件，
兩者皆成立才會啟動（`src-tauri/src/ipc/mod.rs:67-88`）：

1. 目前工作階段為 Premium 方案（`is_premium_tier()`），且
2. **設定**中的 **MCP 伺服器**選項已明確啟用。

此設定預設為 `false`（`src-tauri/src/settings/mod.rs:575`，註解為
「預設關閉（Pro 限定）」），且 Pro 檢查採取失敗即拒絕的原則：若尚無
應用程式控制代碼或工作階段，`is_premium_tier()` 會回傳 `false`
（`src-tauri/src/ipc/mod.rs:95-101`) - 用程式碼自己的話說，就是「寧可
不公開本機伺服器」。

要判斷這是真正的閘門還是只是介面上的開關，有兩個細節很重要：

* 這個閘門是在 Rust 端強制執行，而非前端。支援設定開關的 Tauri 指令
  `set_mcp_server_enabled`，會在開啟設定前於伺服器端重新檢查 Pro 方案，
  不受前端所認為的狀態影響（`src-tauri/src/settings/mod.rs:840-857`）。
* 手動編輯設定檔嘗試繞過同樣行不通：用於 YAML 匯入/協調的通用設定修補
  路徑，在啟動伺服器前會再次檢查 Pro 方案，正是為了封堵這條路徑
  （`src-tauri/src/settings/mod.rs:802-814`，註解：「免費方案使用者
  匯入/編輯 yaml 開啟此設定，絕不能真的打開這個埠」）。

即時切換此設定會立即啟動或停止伺服器（不需重啟應用程式），關閉時也會
如上所述移除持有者權杖。

## 五個工具能做什麼、不能做什麼

MCP 伺服器（`echophrase mcp`）僅公開五個工具，透過 `#[tool_router]`
定義在固定方法集合上（`cli/src/commands/mcp.rs:93-156`）：`record`、
`stop`、`status`、`transcode`、`transcribe`。內部還有第六個子指令
`worker`，用來執行背景錄音行程，但從未註冊為工具
（`cli/src/commands/mcp.rs:6-9`、`cli/README.md:150`) - 代理無法呼叫任何
工具對應到它。

已核實的能力邊界：

* **無法任意讀取檔案。** `transcribe` 接受一個 `wav_path` 字串，但 HTTP
  處理常式在碰觸轉錄流程前會先驗證：必須能透過 `canonicalize()` 解析
  （這也會消除任何符號連結的花招）、必須存在、必須是檔案、且副檔名
  必須是 `.wav`，否則請求會在任何檔案內容被模型讀取前，直接被拒絕為
  `400 Bad Request`（`src-tauri/src/ipc/server.rs:163-186`，
  `validate_wav_path`）。回應內容僅有轉錄文字，絕不包含檔案內容、
  目錄列表，或任何其他檔案系統資訊。
* **無法執行程式碼或呼叫 shell。** HTTP 路由器（`src-tauri/src/ipc/server.rs`）
  與工具路由器（`cli/src/commands/mcp.rs`）都沒有任何程式路徑會啟動子行程、
  將字串當作程式碼執行，或把使用者輸入傳給 shell。CLI 中唯一與外部行程
  的互動，是它自己為了 `record`/`stop` 而產生的錄音背景工作行程，
  且不接受任何來自網路的未受信任輸入。
* **`record`/`stop`/`status`/`transcode` 從不觸及網路。** 它們完全透過
  `crate::ops`（`cli/src/ops.rs`）對本機檔案與本機 `cpal` 音訊裝置運作，
  與桌面應用程式或其 IPC 伺服器是否正在執行無關。只有 `transcribe`
  會與桌面應用程式溝通，且是透過上述持有者權杖保護的本機連線進行。
* **`transcribe` 的請求內容是檔案路徑，而非音訊本身。** CLI 讀取權杖後，
  會將 `{"wav_path": "<本機路徑>"}` 以 POST 送至
  `http://127.0.0.1:1424/v1/transcribe`（`cli/src/ops.rs:315-329`）。
  桌面行程會自行從磁碟讀取 WAV 位元組；音訊內容在整個往返過程中都
  不會經由 socket 序列化傳輸，來回只有一個路徑字串與一段轉錄文字。

## 音訊與轉錄結果會流向哪裡

轉錄作業在已經執行中的桌面應用程式行程內進行，使用它已載入的後端
（Candle、ONNX/parakeet-rs）在本機 GPU/CPU 上推論 - 與桌面介面本身的
聽寫功能使用完全相同的程式路徑。轉錄後端的任何實作中都沒有 HTTP 用戶端
（`src-tauri/src/transcription/{mod,candle_whisper,onnx_backend,parakeet}.rs`
除了文件註解中的網址外，不含任何網路呼叫），因此在這個流程中，音訊、
路徑或最終文字都不會傳送到任何 Echophrase 伺服器。這裡涉及的唯一網路
流量,就是同一台機器上 CLI/MCP 行程與桌面應用程式之間的 loopback HTTP
請求。

這與產品更廣泛的設計原則一致（與 MCP 無關）：語音處理在本機完成，
Echophrase 的伺服器端僅用於驗證與訂閱管理，絕不處理音訊。

## 誠實面對限制

* **權杖檔案的保護程度，僅等同於您的作業系統帳號。** 它的權限位元
  （Unix 上為 `0600`）能阻擋共用機器上其他*使用者*讀取，但任何以*您*
  的身分執行的行程 - 行為異常的瀏覽器擴充功能、其他應用程式中受感染的
  相依套件、惡意軟體，或您已授予廣泛檔案系統存取權的另一個 AI 代理 -
  都能讀取您終端機能讀取的同一個檔案。這與您的作業系統使用者帳號屬於
  相同的信任邊界，而非更強的邊界。若您不信任以您身分執行的任意本機
  行程,那麼也不該信任這個權杖檔案對它們保密 - 而這其實是幾乎所有採用
  「本機權杖 over loopback」設計的開發工具都有的共通事實，並非
  Echophrase 特有。
* **我們未驗證 Windows/macOS 的檔案 ACL。** `0o600` 權限設定呼叫是
  `#[cfg(unix)]` 條件式編譯（`src-tauri/src/ipc/mod.rs:187-191`）；在
  Windows 與 macOS 上，權杖檔案繼承的是您每位使用者資料目錄的預設
  作業系統權限，我們在此並未獨立稽核。
* **硬崩潰可能讓過期的「running」狀態檔案殘留。** 用來回報伺服器
  「為何」停止服務的狀態檔（`not_pro` / `disabled` / `running`），
  只會在正常關閉時清除；若行程硬崩潰，可能留下顯示 `"running"` 的
  狀態,即使行程實際上已不存在。這不會造成安全性後果 - 真正把關請求的
  是權杖檔案，已死亡的行程無論如何都無法回應請求 - 但既然我們說過會
  誠實揭露落差而非只講保證，這一點值得一提
  （`src-tauri/src/ipc/mod.rs:252-265`）。
* **本頁描述的是我們於 2026-07-30 讀取的程式碼。** 撰寫本文時 IPC
  模組正在積極開發中（正在進行的變更，是為了讓 CLI 錯誤訊息更友善而
  新增狀態檔）；本頁在設計層級的說法（僅本機、每次啟動全新權杖、
  停止即移除、Pro 限定、失敗即拒絕）是我們預期會保持穩定的部分，
  但請將具體行號視為當下快照，而非永久保證。

<Card title="MCP 伺服器" icon="robot" href="/zh-Hant/mcp">
  返回工具總覽與設定說明。
</Card>
