# 可选插件不能拖死核心会话：MCP 服务的启动隔离与平滑降级

Source: https://blog.ferstar.org/posts/mcp-server-startup-isolation-and-graceful-degradation/





随着 Model Context Protocol（MCP）生态的铺开，在 Agent 运行时里挂载几个本地或远端 MCP Server 已经成了标配。

但实际用起来，多 MCP 很容易暴露出一个脆弱点：

> **你在配置里配了 5 个 MCP Server。其中本地文件读写、代码搜索几个核心工具都好好的，但偏偏有个做辅助翻译或网页检索的第三方 MCP Server 因为网络抖动超时了，或者本地某个 Python/Node 依赖版本不对直接报错。**
>
> **结果，整个 Agent 进程在启动阶段直接 Panic 崩溃，你连最基础的本地对话和代码问答都用不了。**

一个非核心的可选插件挂了，直接把整个基础会话拉去陪葬。

为了解决这个问题，我们在运行时里做了 MCP 服务的并发隔离和降级处理。

```mermaid
flowchart TD
  subgraph Config[MCP 服务配置分级]
    C1[核心服务: 本地文件读写与终端执行]
    C2[可选服务: 远端知识库与搜索插件]
  end

  subgraph Startup[并发隔离启动沙箱]
    C1 --> T1[Tokio 任务 1: 核心服务, 校验严格]
    C2 --> T2[Tokio 任务 2: 独立 3s 超时限制]
    C2 --> T3[Tokio 任务 3: 独立 3s 超时限制]
  end

  subgraph Outcome[聚合与动态降级]
    T1 -->|启动成功| R[动态工具注册表]
    T2 -->|超时或异常| D[记录降级告警, 不阻断主流程]
    T3 -->|启动成功| R
    D -.->|过滤不可用工具| R
    R --> S[会话正常启动 / 前台提示降级运行]
  end

  Config --> Startup
```

---

## 1. 以前的做法有什么毛病？

过去很多 MCP Client 初始化的写法就是一个简单的串行循环：

```rust
// 以前的串行脆弱写法
for server_config in mcp_servers {
    // 只要其中任何一个 connect 或 list_tools 挂了，整段逻辑直接 return Err
    let client = McpClient::connect(&server_config).await?;
    let tools = client.list_tools().await?;
    registered_tools.extend(tools);
}
```

问题很直白：
1. **启动时间累加**：启动耗时等于所有 MCP Server 握手耗时的总和。只要有一个慢服务卡 5 秒，整个 Agent 启动就要等 5 秒。
2. **缺乏故障隔离**：核心文件工具和可选的翻译工具处于同一生命周期，一旦外部服务报错，异常直接冒泡导致全盘崩溃。

---

## 2. 怎么改？

重构后的逻辑主要围绕三点：

### 1. 显式区分核心服务与可选服务

在配置里加上关键度标记：

```json
{
  "mcpServers": {
    "filesystem": {
      "command": "agent-mcp-fs",
      "required": true
    },
    "web_search": {
      "command": "uvx",
      "args": ["mcp-server-duckduckgo"],
      "required": false,
      "timeout_ms": 3000
    }
  }
}
```
- `required: true`（核心）： Agent 必须依赖它才能工作，挂了就明确报错退出；
- `required: false`（可选，默认）：启动失败或超时只记日志并熔断，不影响主会话。

### 2. 并发探测与独立超时

用 Tokio 为每个 MCP Server 单独开一个异步任务（`tokio::spawn`）并发探测，并套上独立的 `timeout`：
- 所有 Server 同时握手，冷启动耗时取决于最慢的那个，而不是全部累加；
- 可选服务如果在 3 秒内没连上或进程异常退出，捕获错误并将其标记为 `Degraded`，绝不往外抛异常。

### 3. 动态工具过滤与前端感知

探测任务全部结束后：
1. 把握手成功的工具注册进会话上下文；
2. 过滤掉降级服务的工具，防止模型误调用；
3. 向前端发送一个轻量提示，告诉用户哪个插件启动失败了，但主会话依然可以正常使用。

---

## 3. 总结

改完之后，哪怕本地断网或者某个 MCP 插件配置写错了，Agent 的基础功能依然能秒级拉起。

依赖外部环境的插件系统，必须做好隔离与降级。不要让一个锦上添花的边缘小功能，影响了核心工具的可用性。

