# 在信任服务器前审核 MCP 客户端配置

新的 MCP 服务器值得像一个准备在开发账户中运行的新可执行文件一样接受检查。配置文件看起来可能很无害，因为里面只有一条命令和几个参数。但实际上，这一条配置决定了哪些代码会启动、进程会获得哪些环境变量、能够访问哪些目录，以及会向代理提供哪些工具描述。

我经常看到的错误，是把「本地」当成信任边界。它不是。本地服务器通常以你的用户权限启动，可以在公布第一个工具之前执行操作，还可能继承原本没人打算分享的凭据。客户端连接前，先审核启动契约。然后在代理能够调用工具前，再审核服务器公布的操作。

## 本地服务器会承担你账户带来的后果

本地 MCP 服务器是一个子进程，不是无害的配置对象。如果客户端以你平时的 macOS 账户启动它，服务器可能读取可访问的项目文件、写入主目录、发起出站网络请求，并检查该进程能够看到的环境变量。MCP 不会为服务器提供沙箱。协议只负责传递消息，不会限制可执行文件在消息之间做什么。

当服务器通过包命令提供时，这个区别尤其重要。下面的配置：

```json
{
  "command": "npx",
  "args": ["-y", "some-mcp-server"]
}
```

做的不只是启动一个已知的本地二进制文件。它要求包运行器解析一个包，从缓存中安装或复用代码，然后启动它。干净的机器、已经有缓存的机器，以及发生变化的包标签，可能得到不同的代码。这个熟悉的命令常让人跳过真正重要的问题：今天究竟会运行哪个确切的可执行文件？

如果服务器代码放在项目旁边，风险同样存在。一个代码仓库可以包含诚实的 MCP 实现，也可以包含恶意的安装钩子、包装器或运行时依赖。要检查启动进程的路径，而不只是安装指南中出现的那个源文件名。

先把操作系统账户当作第一道隔离选择。一次性工作区、单独的低权限账户或虚拟机，可以让早期测试接触到的内容少于那个存放生产源代码和云凭据的账户。这不能替代代码审核，但当审核漏掉某些问题时，它能限制损失。

## 命令字段需要按字面阅读

严格按照客户端实际执行的方式阅读命令和参数。不要在脑中把它们转换成 README 里的友好说明。

更安全的形式是固定的可执行文件路径和参数数组：

```json
{
  "command": "/Users/dev/tools/acme-mcp/bin/server",
  "args": ["--config", "/Users/dev/review/acme-mcp.json"],
  "env": {
    "HOME": "/Users/dev/review-home",
    "PATH": "/usr/bin:/bin"
  }
}
```

这种形式提供了明确的审核点。这个路径上的可执行文件存在吗？谁拥有它？配置文件是否放在其他人可以修改的代码仓库之外？进程真的需要 `HOME` 吗？它确实需要编译器、包管理器或宽泛的 `PATH` 吗？

包装器会改变审核重点。再看这个配置：

```json
{
  "command": "sh",
  "args": ["-c", "npx -y acme-mcp --token $SERVICE_TOKEN"]
}
```

现在 shell 会展开 `$SERVICE_TOKEN`、读取 shell 语法，还可能运行超出预期的程序。包运行器可能下载代码。服务器会把密钥作为命令参数接收，而它可能出现在进程检查结果和诊断输出中。每一层都有额外行为，而直接使用可执行文件路径可以避开这些行为。

不要假设每个客户端执行 `command` 的方式都相同。有些客户端使用带参数数组的直接进程启动，有些提供面向 shell 的设置，或者允许使用包装脚本。阅读客户端文档，并先用不敏感的命令测试启动，再批准真正的命令。如果格式同时支持直接执行和 shell，除非服务器有你能说明的明确需求，否则选择直接执行。

逐行检查包装器。小脚本经常藏着最危险的行为：下载发行版、读取令牌文件、改变工作目录、导出全部环境变量，或悄悄通过另一个运行时重新启动。一个二十行的启动器可能比服务器的主要实现更值得关注。

## 继承的环境是人们最容易漏掉的凭据泄露点

`env` 代码块并不可靠地表示「这就是完整环境」。在许多进程启动 API 中，父进程环境会继续传递，除非启动器明确替换它。`env` 中的条目只是添加或覆盖选定的值。你的 MCP 客户端本身可能从终端、桌面启动器、编辑器或自动化服务启动，而每条路径都可能提供不同的变量。

这会在审核者看到的内容和服务器实际接收的内容之间制造危险差距。JSON 可能只列出 `LOG_LEVEL`，但进程还可能获得包仓库令牌、代码托管令牌、云凭据、代理设置、SSH agent 详情，以及从客户端继承的内部服务 URL。

连接前，用简单的语言写下环境契约：这个进程需要这个端点、这个非敏感设置，以及最多一个范围很窄的凭据。其他一切都是意外获得的权限。

第一轮测试可以从稀疏 shell 启动客户端。下面的 macOS 和 Unix 命令只保留几个普通变量：

```sh
env -i HOME="$HOME/review-home" PATH="/usr/bin:/bin" LANG="${LANG:-C}" \
  YOUR_MCP_CLIENT
```

将 `YOUR_MCP_CLIENT` 换成实际的客户端命令。如果服务器失败，一次只添加一个变量，并记录它为什么需要该变量。不要通过恢复整个登录环境来解决失败。这个捷径暴露的凭据比人们意识到的更多。

你也可以在不执行任何命令的情况下检查保存的配置。下面的 Python 片段会打印服务器名称、命令、参数和明确配置的环境变量名称。它刻意不会打印环境变量值。

```sh
python3 - "$HOME/.config/your-client/mcp.json" <<'PY'
import json, sys

with open(sys.argv[1], encoding="utf-8") as f:
    data = json.load(f)

for name, spec in data.get("mcpServers", {}).items():
    print(f"server: {name}")
    print(f"  command: {spec.get('command', '')}")
    print("  args:")
    for arg in spec.get("args", []):
        print(f"    - {arg}")
    print("  explicit env names:")
    for env_name in sorted(spec.get("env", {})):
        print(f"    - {env_name}")
PY
```

输出大致如下：

```text
server: issue-tracker
  command: /Users/dev/tools/issue-mcp/server
  args:
    - --read-only
  explicit env names:
    - ISSUE_TRACKER_URL
```

如果看到 `AWS_SECRET_ACCESS_KEY`、`GITHUB_TOKEN`、`SSH_AUTH_SOCK` 或 `SERVICE_TOKEN` 之类的名称，先停下来问问这个服务器为什么需要它们。秘密不会因为放在 JSON 文件里而不是源代码里就变得安全。配置文件可能被复制进备份、在支持请求中共享、意外提交，或被所有能读取该文件的进程读取。

## 工具列表是能力声明，不是权限授予

Model Context Protocol 规范定义了用于发现的 `tools/list` 和用于调用的 `tools/call`。这很有用，因为客户端可以在代理选择工具前检查服务器提出的接口。但它不会认证服务器、工具描述或调用背后的副作用。

把每个公布的工具都当成一项待审核的能力。一起阅读它的名称、描述、输入模式和所有注释。名为 `search_issues` 的工具可能只发起只读请求，也可能在搜索前收集项目文件并发送给第三方。名为 `deploy_preview` 的工具可能创建资源、修改 DNS，或使用范围比名称暗示的更广的凭据。

MCP 规范允许服务器提供一些提示行为的注释，例如工具是否读取数据、修改数据或与外部系统交互。这些提示能帮助客户端展示有用的界面，但规范不会把它们变成强制约束。不诚实或粗心的服务器可以把破坏性工具标记为只读。操作系统和服务器的凭据也不会在操作执行前检查这个标签。

为每个批准的服务器建立一份小型清单：

- 工具名称，以及它声称要执行的操作。
- 能够携带文件路径、URL、shell 片段或自由文本提示的输入。
- 工具可以访问的系统，以及它使用的凭据。
- 副作用，包括向远程 API 发送数据等间接影响。
- 你审核过的确切版本或源代码修订。

把清单放在配置附近。当更新加入 `delete_repository`、把 `query` 改成 `execute`，或新增一个接受任意 URL 的输入时，差异比较才有意义。没有之前的清单，人们常会因为服务器名称看起来仍然熟悉，就批准发生变化的工具列表。

工具描述和代理收到的其他不可信文本一样值得怀疑。服务器可以把某个工具描述成必需，声称不需要批准，或指示代理把无关的秘密作为参数传入。代理不应把服务器提供的文字看得比用户请求和客户端审批规则更有权威。

## 凭据需要在代理进程之外建立边界

不要因为服务器和你在同一台笔记本电脑上运行，就把长期有效的 API 令牌或私有 SSH 密钥交给它。服务器进程一旦拿到秘密，就可以记录、转发、写入磁盘，或通过工具结果暴露它。进程读取秘密后，客户端无法把它收回来。

把团队经常混淆的两个决定分开。允许启动服务器，意味着允许执行代码。允许使用生产凭据，意味着允许对外部系统采取行动。服务器可能在测试工作区中值得获得第一项权限，但显然不值得获得第二项权限。

对于简单的开发用途，发放只能接触测试数据的范围有限、有效期很短的凭据，并把范围写清楚。「由问题服务器使用」太模糊；「可以读取沙盒项目中的工单，不能创建、评论或修改成员关系」才给审核者留下了可测试的标准。

对于需要重要凭据的操作，把秘密留在本地凭据边界内，只暴露范围狭窄的操作。Sallyport 对 HTTP 和 SSH 操作采用了这种方式：代理不会收到 API 密钥或 SSH 密钥，由应用执行操作并返回结果。

这个边界改变的是秘密处理方式，并不能消除服务器审核的必要性。恶意服务器仍可能要求代理发起有害但得到授权的调用。把审批放在后果发生的地方，把凭据限制在最小的有效权限内，并阅读每个进入重要系统的请求。

不要把凭据放进命令参数。进程列表、崩溃报告、诊断信息和父进程都可能暴露它们。出于同样原因，也要避免在配置文件中保存明文秘密。如果安装指南要求把秘密放在其中任一位置，先确认服务器能否使用操作系统凭据存储、短期令牌或外部操作服务，再接受这种设计。

## 第一次接触应发生在普通的测试账户中

在给陌生服务器项目、宽泛环境或真实凭据之前，先在受控账户中运行它。这次测试只回答一个有限问题：程序启动时，以及客户端请求工具时，它会做什么？

使用一个新目录，放入名称明显、内容无害的文件，让意外读取很容易被发现。给进程一个临时 `HOME`。从稀疏的 `PATH` 开始。不要因为想测试代码托管服务器，就挂载一个装满源代码的目录。先使用虚假仓库，或使用不含凭据的副本。

观察工具调用之前的行为。服务器如果在启动时打开网络连接、扫描主目录、读取浏览器数据或创建持久化文件，就已经超出大多数 MCP 用例的需要。有些服务器确实需要检查端点或加载本地配置，但这些行为应当容易解释，也容易关闭。

然后请求工具清单，检查它，并用已知输入执行一次无害调用。记录请求、结果、进程输出以及测试目录中发生变化的文件。如果工具返回另一个系统的内容，让测试数据足够醒目，这样你能判断它是否访问了正确位置。

基本审核流程如下：

1. 核实可执行文件路径、包版本、校验和或源代码修订，以及每个启动脚本。
2. 在测试账户或隔离工作区中，使用稀疏环境启动。
3. 获取第一次 `tools/list` 结果，并将每个工具与预期工作进行比较。
4. 使用虚假数据调用一次无害的读取操作，观察文件、子进程和网络目的地。
5. 只添加已确认行为确实需要的凭据和目录访问权限。

不要把成功响应误认为安全服务器。服务器可以返回预期答案，同时在别处复制文件或使用继承的令牌。测试提供的是证据，不是证明。它能在服务器获得生产权限前，发现粗心的设计和明显的意外。

## 包的便利性带来了你必须负责的更新路径

包管理器和运行时启动器让 MCP 设置变得很短，也会带来一条更新路径。版本范围、浮动包标签或不带版本的包名，可能让下周启动的服务器发生变化，而配置文件却没有任何差异。

在生态系统支持时固定版本，并把包来源和工具清单放在一起记录。更好的做法是使用经过审核的本地制品，或使用团队已经检查的锁定文件。实际目标很简单：下一次启动时，解析出的代码应当是你能够识别的代码。

不要让代理在任务过程中自行安装 MCP 服务器。这样会把软件获取、代码执行和工具授权合并成一个对话请求。人应当在审核源代码及其启动契约后，再添加服务器配置。如果开发流程需要许多服务器，应维护一份经过审核的目录，而不是接受问题评论或工具输出中的安装片段。

更新需要进行一次简短的重复审核。比较可执行文件或依赖锁定文件、命令和参数、明确配置的环境变量名称，以及 `tools/list` 清单。新增工具可能无害，也可能引入此前从未考虑过的写入权限。服务器改变凭据、端点或身份验证库时同样如此。

「为了安全修复，始终使用最新包」这个常见建议对 MCP 服务器来说并不完整。你应及时采用安全修复，但未经审核的自动变更可能改变运行在凭据旁边的代码。采用可控的更新流程，让你能够检查变化，并在新服务器行为不同的时候回滚。

## 审批应跟随操作，而不是服务器名称

一次性批准服务器进程，只回答一个问题：这个可执行文件能否参与本次会话？它不能安全地回答之后所有问题，例如是否可以删除远程分支、发送客户数据，或在生产主机上打开 SSH 命令。

把普通发现和有后果的操作分开。列出可用项目、读取公开问题和获取本地模式，通常不需要像修改记录或向机器外发送数据那样严格的检查。客户端或凭据边界应在调用越过这条线时要求新的人工作批。如果每次读取调用都要求注意，人们会习惯性点击通过。如果一次启动点击就授予无限生产权限，人们迟早会后悔。

进程身份同样重要。审批提示应告诉你哪个已签名进程请求了访问，而不只是显示服务器自行选择的标签。任何程序都可以复制 `database-helper` 这样的标签。可执行文件路径、可用时的签名身份以及启动参数，才是更好的审核界面。

在两个层面保留记录：请求工作的代理会话，以及使用凭据或访问外部服务的单次操作。调查代理为何拥有权限时，需要会话记录。调查实际发生了什么变化时，需要操作记录。如果日志缺少调用者或确切请求，一份日志无法清楚回答这两个问题。

防篡改记录有助于在服务器、客户端或操作者之后对事实提出异议时还原情况。但它不会让危险操作在批准当下变得安全。批准无法轻易撤销的操作前，阅读目标、方法、命令和有意义的参数。

## 拒绝服务器后需要清理，而不只是删除

如果连接服务器后判断它不可信，立即删除客户端条目，但不要到此为止。进程可能已经写入文件、修改 shell 配置、创建计划任务、改动代码仓库钩子，或在运行期间复制凭据。

删除一切之前先保留证据。保存配置、包版本或源代码修订、进程输出和操作日志。然后检查测试用的 `HOME`、服务器工作目录、包缓存、shell 启动文件、启动代理、代码仓库钩子，以及服务器可以写入的目录。在证据仍然存在时，检查活动进程和近期网络连接。

轮换进程可能读取过的每个秘密，而不只是你有意提供的那个。这包括继承的令牌、SSH agent 访问权限、包凭据，以及相关情况下由浏览器支持的开发会话。删除配置文件中的一行，并不会撤销已经被复制的令牌。

然后收紧允许这次连接发生的审核流程。如果问题来自继承的环境，就使用稀疏启动器。如果问题来自意外的包更新，就固定并审核制品。如果问题来自误导性的工具描述，就要求在批准前保存工具清单。真正有用的结果是控制措施发生变化，而不是含糊地承诺下次会更小心。

新的 MCP 服务器在最危险的时刻获得访问权限，也就是它还没有赢得任何信任之前。让命令保持明确，让环境尽可能小，检查工具列表，并让凭据与代理分离。此时你仍然能够控制结果。
