# 删除已废弃的 MCP 服务器，同时不留下访问权限

已废弃的 MCP 服务器应该和废弃的部署脚本一样处理：在证明它已经失效之前，先假设它仍然可以工作。一个过时的配置项可能启动本地命令、把代理引向远程端点、暴露旧环境变量，或者保留一条通往无人负责凭据的路径。

我见过开发者删掉一个配置块，就认为工作已经完成，几个月后却发现同一个工具仍然会从编辑器扩展或仓库文件中启动。解决办法不是再做一张更大的表格。你需要把发现、撤销、删除和证明访问路径已经停止分开处理。

## 删除配置不等于撤销访问路径

删除已废弃的 MCP 服务器，意味着关闭所有允许代理执行操作的路径，而不只是从某个客户端菜单中隐藏服务器。服务器定义只是这条路径的一部分。

典型的本地设置包含四个部分：客户端配置、启动命令、传给该命令的配置或环境变量，以及目标服务上的权限。远程设置会用 URL 替代本地命令，但客户端配置和远端权限仍然存在。团队经常只删除第一部分，却把其余部分原封不动地留下。

Model Context Protocol 规范将服务器描述为提供工具、资源和提示的能力提供者。它也支持多种传输方式，包括 stdio 和基于 HTTP 的连接。服务器退役时，这个区别很重要。stdio 服务器可能只有在本地客户端启动命令时才会运行。远程服务则可能在每个开发者都删除本地配置后继续可用。

不要混淆下面几类对象：

- **配置记录**告诉某个客户端去哪里寻找服务器。
- **启动器**是启动或连接服务器的可执行文件、脚本、容器命令、扩展或服务。
- **权限授权**是让操作成功的令牌、SSH 身份、OAuth 授权、账户会话或网络权限。
- **执行记录**显示某个客户端或代理确实使用过这条路径。

弄错这些概念会导致两种糟糕的结果。你可能让旧工具继续访问生产数据，也可能撤销一个仍被活跃工具使用的凭据，把例行清理变成事故。

先制定一条真正有效的退役规则：如果服务器没有当前所有者、记录在案的用途，也没有近期有意使用的证据，就在调查期间先停用它。「以后可能会用到」不等于有人负责。如果将来确实需要重建，仓库可以私下保留设置，但必须使用新的凭据。

## 先盘点客户端，再搜索磁盘

第一份清单应该列出客户端，而不是服务器，因为每个客户端读取配置的位置都不同。开发者通常安装了不止一个代理客户端，还可能有编辑器集成、终端辅助工具，以及和代码一起提交的项目级设置。

写下机器上所有可以发起 MCP 连接的位置。包括桌面应用、命令行代理、IDE 扩展、调用代理的本地脚本，以及会挂载主目录的远程开发环境。询问开发者平时实际使用什么，然后进行核实。原型阶段运行过一次、如今已经过去六个月的工具，不能靠记忆判断。

对每个客户端记录版本、其文档中列出的配置位置，以及它是否支持用户级和项目级设置。不要因为另一个客户端使用过某个路径，就猜测当前客户端也使用它。配置位置会变化，凭空写出的路径只会制造虚假的安全感。

一条有用的清单记录应包含足够的信息，方便之后做出退役决定：

| 字段 | 记录内容 |
|---|---|
| 客户端和配置路径 | 哪个程序读取该文件，以及文件在哪里 |
| 服务器名称 | 代理或用户看到的标签 |
| 传输方式和启动器 | stdio 命令、URL、扩展、容器或脚本 |
| 所有者和用途 | 负责此项的人员，以及它支持的当前工作 |
| 目标系统 | 可访问的 API、主机、仓库、数据存储或本地文件夹 |
| 权限来源 | 令牌存储、环境变量、SSH 身份、OAuth 授权或托管身份 |
| 决定 | 保留、替换、暂停或退役 |

项目设置最容易带来意外。开发者可以清理主目录中的配置，却仍然会在打开旧仓库时启动服务器。搜索当前分支、用于本地设置的忽略文件、示例配置文件和入门脚本。也要检查共享的 dotfile 仓库。旧服务器经常因为有人把一段方便的代码片段复制进模板而一直存在。

不要把清单当成合规材料。用它推动实际行动。如果你说不清某个服务器的目标系统和权限来源，就把它标记为未解决，并阻止日常使用，直到查清为止。

## 搜索启动器，而不只是搜索名为 MCP 的文件

搜索「mcp」可以找到明显的配置，但启动器可能藏在通用脚本名称和软件包 bin 目录中。搜索清单中出现的服务器标签、命令名、主机名、端口、软件包名称和环境变量名称。

在 macOS 上，下面的命令可以从主目录中列出一批可能的 JSON 配置文件，作为有限范围的起点。它会跳过缓存目录，否则那里会产生大量无关的软件包元数据。

```sh
find "$HOME" -type f \( -name '.mcp.json' -o -name 'mcp.json' -o -name '*mcp*.json' \) \
  -not -path "$HOME/Library/Caches/*" \
  -print 2>/dev/null
```

输出可能类似这样：

```text
/Users/dev/work/acme-api/.mcp.json
/Users/dev/Library/Application Support/example-client/settings.json
/Users/dev/.config/example-agent/mcp.json
```

这份列表是证据，不是删除清单。打开每个文件，确认哪个客户端拥有它。文件名中含有 `mcp` 的文件可能是文档、归档实验或生成的锁文件。反过来，一个名称普通的设置文件也可能包含真正的服务器定义。

对于架构使用 `mcpServers` 对象的 JSON 文件，下面的命令会输出一张简洁的检查表，不会修改文件。

```sh
jq -r '
  .mcpServers // empty
  | to_entries[]?
  | [.key, (.value.command // .value.url // "unknown"),
     ((.value.args // []) | join(" "))]
  | @tsv
' path/to/config.json
```

典型结果如下：

```text
issue-tracker	npx	-y @example/issues-mcp
legacy-reporting	https://reports.internal.example/mcp	
```

如果命令没有输出，不要因此推断系统安全。文件可能使用了其他架构，客户端可能把设置存放在别处，或者服务是通过扩展提供的。

接着检查普通文件清理之后仍可能存在的启动入口。在 Mac 上查看 Shell 配置文件、编辑器任务设置、包管理器的全局二进制文件和用户启动代理。`launchctl print gui/$(id -u)` 可以显示以当前登录用户身份启动的进程，但其输出可能暴露命令参数或环境值。请只在本地查看，不要把输出粘贴到工单或聊天中。

使用范围窄的关键词搜索内容，不要扫描并导出整个主目录。例如，确定一个已废弃的服务器调用 `old-report` 后，搜索这个完整名称、旧主机名和可执行文件。这样可以找到 `scripts/agent-tools.sh` 这样的包装脚本，又不会让清理审查变成私人数据收集活动。

## 按当前权限对服务器分类

服务器看起来已经停止使用，却可能仍保留有效权限。因此，在处理文件之前，先按身份验证方式分类。同一个服务器名称在不同机器上可能使用不同凭据，这也是团队级删除需要逐台提供证据的原因。

可以使用下面五类。它们描述权限所在的位置，而不是服务器对外宣传的方式。

1. **没有远程权限。**服务器读取本地非敏感文件或生成本地输出。它仍可能造成供应链或隐私问题，但撤销通常意味着删除进程权限和配置。
2. **由环境变量提供权限。**启动器通过 Shell 配置文件、`.env` 文件、IDE 设置或启动配置接收 API 令牌、密码或连接字符串。
3. **由文件提供权限。**启动器读取 SSH 私钥、客户端证书、服务账户文件或本地凭据数据库。
4. **由服务提供商管理的权限。**服务器使用 OAuth、应用安装授权、设备登录或托管身份。授权由服务提供商控制，而不是由某个本地文本文件控制。
5. **网络和账户权限。**服务器不需要明确的密钥，因为企业网络、本地账户、VPN 会话或允许列表中的地址让它可以访问服务。这类权限很容易漏查，也很难干净地退役。

为每个权限来源记录准确的账户、范围和目标。「Git 令牌」远远不够。你需要知道它属于个人账户、机器人账户还是共享机器身份，也要知道它能读取仓库、创建问题、触发部署，还是访问管理 API。

清理工作往往会暴露一些不舒服的捷径。一个通过 `~/.zshrc` 接收宽泛个人令牌的本地 MCP 命令，不会因为开发者不再使用它就变得无害。这个令牌可能还被其他脚本使用，因此撤销需要协调。这不是推迟工作的理由，而是先梳理依赖关系再撤销的理由。

保持清单客观。不要把令牌值、私钥、完整授权标头或复制的配置内容放进去。「名为 reporting-read 的凭据条目」或「指纹末尾为 3f:91 的 SSH 密钥」这样的引用，已经足够让正确的人找到权限来源，也不会再制造一个秘密仓库。

## 先在服务端撤销权限，再删除本地证据

先在服务提供商或目标服务处撤销有效权限，再删除本地配置。这样可以阻止复制出的配置、另一台机器或未被发现的启动器继续使用同一授权。

对于 API 令牌，使用服务提供商的令牌管理界面或其文档中说明的撤销端点。通过令牌标识符、标签、账户、创建信息或最近使用记录确认要撤销的是哪个令牌，前提是服务提供商提供这些信息。然后从本地文件和凭据存储中删除其值。不要通过把已撤销的令牌粘贴到网页表单或可能保存命令历史的 Shell 命令中来测试它。

OAuth 需要特别小心。RFC 7009 定义了向授权服务器撤销端点发送的令牌撤销请求。即使令牌无效，服务器也可以返回成功响应，从而避免调用方得知令牌是否存在。因此，仅凭 200 状态码不能证明你撤销的是目标授权。检查服务提供商的授权或已连接应用页面；只有在符合日常安全规范时，才通过旧路径执行一次受控调用。

对于 SSH，从所有接受该密钥的位置删除公钥或部署密钥。这些位置可能包括账户的 authorized keys、仓库部署密钥设置、跳板机账户、CI 服务，以及会重新填充 `authorized_keys` 的配置管理源。删除 `~/.ssh/old_agent_key` 只会删除一份本地副本，对其他副本和服务器端授权都没有影响。

对于应用安装授权和服务账户，停用或删除安装授权；如果存在暴露可能，就轮换客户端密钥或私有凭据；同时删除只为已退役服务器存在的角色绑定。即使服务账户仍然保留，也要把权限过宽列为清理事项。代理工具很少需要和人类管理员相同的访问权限。

基于网络的访问需要另一种处理方式。只有在确认所有者和使用方之后，才能删除旧的允许列表条目、防火墙规则、VPN 组成员资格或内部 DNS 路由。不要把 MCP 清理工单当成破坏无关集成的授权。应当先隔离具体规则，并设定期限，让所有者确认它是否仍在使用。

把撤销结果记录成一个事件：谁执行了撤销、撤销的是哪个权限标识符、在哪里执行，以及如何验证。不要记录秘密，也不要保存包含秘密的截图。当仓库之后出现故障、有人询问是否由清理引起时，这份记录会很重要。

## 按可回退的顺序删除本地定义

在上游撤销之后删除本地定义，并采用一种既能保留私下回退路径、又不会保留有效凭据的顺序。粗心的卸载会让项目无法解释自己依赖过什么；过度谨慎的归档则可能把可用令牌留在被遗忘的文件夹中。把配置材料和秘密材料分开处理。

一次处理一个服务器时，按下面的顺序操作：

1. 停止相关代理客户端和编辑器扩展。运行中的客户端可能让子进程继续存在，或者在退出时重写设置。
2. 将服务器的非秘密配置字段复制到退役记录中：名称、命令或 URL、参数、预期目标、所有者和删除日期。把秘密值替换成它们所在位置的描述。
3. 在目标服务处撤销权限，并记录确认细节。
4. 从所有已确认的用户级和项目级配置中删除服务器条目。删除环境变量以及对已退役凭据文件的引用。
5. 如果没有活跃服务器使用它，就卸载专用软件包、扩展、容器镜像或包装脚本。如果其他工作仍在使用该软件包，只删除已废弃的命令，并记录共享依赖关系。

避免使用宽泛的搜索替换。JSON 逗号、Shell 引号和共享环境块都经不起随意编辑。如果客户端的设置界面可以稳定地写出有效配置，就使用它。否则，先以严格的文件权限创建备份，编辑一个对象，然后在重新打开客户端前验证结果。

对于 JSON，`jq` 可以进行简单的语法检查：

```sh
jq empty path/to/config.json && echo "valid JSON"
```

这只能证明 JSON 可以解析，不能证明客户端接受该架构，也不能证明你删除了所有引用。编辑后读取相关对象，再使用客户端检查已配置的服务器列表。

不要把完整的 `.env` 文件、私钥或含有 bearer token 的设置文件归档到项目的 `archive` 目录中。版本控制、云备份和桌面搜索会让这个错误传播到很远。保存经过删减的记录，并依靠服务提供商的审计历史证明之前确实存在过该凭据。

删除软件包也要保持克制。全局安装的运行时软件包可能被多个活跃工具使用。删除前，确认每个仍然有效的配置调用的是哪个可执行文件。我见过清理工作删除共享运行时依赖，随后另一个团队不得不排查损坏的代理会话，因为错误信息提到的是缺少软件包，而不是被删除的工具。

## 证明没有任何东西会启动或访问已退役服务器

当相关客户端无法发现、启动或通过身份验证访问服务器时，退役才算完成。仅检查配置无法证明其中任何一点。

彻底重启客户端。关闭窗口可能无法停止菜单栏辅助程序、编辑器宿主进程或子进程。重新打开客户端，使用其正常诊断功能检查已配置的服务器列表。如果已退役服务器仍然出现，说明你漏掉了配置来源，或者某个同步机制把它恢复了。

接着执行范围有限的启动测试。打开原先提供服务器配置的仓库，启动代理，并要求它执行一个与已退役服务器无关的无害操作。观察本地进程活动中是否出现已退役的可执行文件名称，并检查客户端日志中是否尝试连接旧主机名。不要为了确认失败而让已退役工具调用真实的生产目标。

对于远程端点，使用服务提供商审计记录、访问日志或账户活动，检查撤销后是否仍有尝试使用。受控测试中出现一条被拒绝的请求，可以证明权限已经失效。日志没有记录则是较弱的证据，因为客户端可能根本没有尝试连接。

还要确认旧凭据没有出现在不该出现的地方。搜索令牌标签、环境变量名称、已知主机名、文件名和 SSH 公钥指纹。不要搜索完整秘密值，否则它可能进入 Shell 历史、终端回滚内容或命令日志。标签和引用通常已经足够。

有一种失败模式很值得注意：开发者从个人代理配置中删除 `legacy-reporting`，重启代理后什么也没看到。一周后，他们打开一个旧仓库。仓库的本地设置运行带有旧软件包的 `npx`，脚本从 Shell 配置文件加载 `REPORTING_TOKEN`，而远程 API 仍然接受这个令牌。每一步单独检查都看起来正常，因为每一步只检查了个人配置。开始删除之前，清单就应该把仓库配置、启动器、环境变量来源和 API 授权关联起来。

## 保留证据，但不要制造另一个秘密缓存

保留足够的证据来解释一次退役，但不要把审计目录变成可用访问权限的档案库。这份记录应当让其他工程师回答：曾经存在什么、它能访问什么、谁批准了删除，以及什么证据证明工作已经结束。

一份简洁的退役记录可以包括服务器标识符、删除的本地位置、不含凭据的命令或端点、目标服务、权限类型、授权标识符或指纹、撤销日期、所有者和验证结果。把需要访问控制的操作记录放在团队已有的安全记录系统中。不要创建一份塞满复制设置的新共享文档。

执行历史可以暴露清单遗漏的依赖关系。查看代理会话历史、客户端日志、包管理器安装历史、添加配置的源代码管理变更，以及目标服务活动。谨慎解读时间戳。日志可以显示某个进程运行过，却不一定能证明工具完成了特权操作。

Sallyport 会把代理运行和每次 HTTP 或 SSH 调用记录在不同日志中，这些日志由不可写的加密审计日志投影生成。如果团队使用 Sallyport，`sp audit verify` 可以在离线状态下直接对密文验证审计链，无需保险库密钥，这有助于在访问审查期间保留证据。

不要把防篡改证据和完整清单混为一谈。日志只会记录经过记录点的操作。它不会发现绕过记录系统运行的旧服务器、从未启动过的遗忘配置，或被其他脚本直接使用的复制令牌。

## 在工具变成考古遗迹前让所有权到期

团队在安装工具时就指定所有者和审查日期，清理会轻松得多。只有当你需要查明一个最初项目几年前就结束的代理为什么仍能访问 API 时，这条规则才会显得不只是行政要求。

当有人添加具有写入权限、生产环境可见性或广泛仓库访问权限的服务器时，要求其提供简短的退役计划。计划应说明所有者、预期使用的仓库、权限类型、目标系统，以及触发删除的事件。原型可以设置较短的审查日期。共享工具可以指定一名维护者。两者都不应拥有无人负责的永久例外。

谨慎使用仓库配置。当项目确实需要项目级服务器，且配置文件中没有嵌入凭据时，这种设置才合适。仓库不是存放开发者个人实验的好地方。把实验放在私密且可随时丢弃的位置，之后要么在明确所有者后正式纳入管理，要么在工作树变成模板之前删除。

在代理客户端变更、团队交接、仓库归档和凭据轮换之后进行审查。这些事件比随意安排的日历提醒更容易暴露配置漂移。如果审查发现未知服务器，先切断它通往敏感系统的路径，再追查所有者和依赖关系。没人能解释的工具不应继续保留执行权限。

下一次清理可以从一台真实机器和一个活跃客户端开始。建立清单，直到每个服务器都有所有者、启动路径和权限来源。无法达到这个标准的条目，已经告诉你哪些东西应该退役。
