> ## 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-CN/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-CN/mcp">
  返回工具总览与设置说明。
</Card>
