---
url: /wave-agent/specs/ecosystem/mcp.md
description: MCP 服务器接入与管理：项目/用户/插件来源配置合并、生命周期控制、`instructions` 播报与设置页增删改
---

# 功能规格说明：MCP 支持

**规格文件**：`docs/specs/ecosystem/mcp.md`
**创建日期**：2026-01-21

## 用户场景与测试 *(必填)*

### 用户故事：通过 MCP 使用外部工具（优先级：P1）

作为开发者，我希望将代理连接到外部 MCP 服务器，以便使用代理未内置的专用工具（如天气、数据库访问或自定义脚本）。

**优先级原因**：这是 MCP 支持的核心功能，实现可扩展性。

**独立测试**：连接到简单的 MCP 服务器（例如 "hello world" 服务器）并验证代理可以列出和调用其工具。

**验收场景**：

1. **假设** 有有效的 `.mcp.json` 配置，**当** 代理启动时，**则** 它必须读取并注册这些服务器，但不得在此刻拉起任何服务器进程——拨号时机见「按需连接 MCP 服务器」。
2. **假设** MCP 服务器已连接且工具池未收敛，**当** 代理列出工具时，**则** 它必须包含带有 `mcp__` 前缀的 MCP 工具。
3. **假设** MCP 服务器已连接且工具池收敛已生效（池非空且 `Exec` 可声明，见 `docs/specs/core/exec-tool.md`），**当** 代理列出工具时，**则** 逐条扁平声明让位于 `Exec` 的目录——撤下的只是"声明"形态，工具本身照常可调用。
4. **假设** AI 决定调用 MCP 工具，**当** 工具执行时，**则** 请求必须发送到正确的 MCP 服务器并将结果返回给 AI。
5. **假设** AI 在 `Exec` 脚本里嵌套调用 MCP 工具，**当** 该调用执行时，**则** 请求必须同样发送到正确的 MCP 服务器、结果回到脚本（与扁平调用共用同一执行漏斗与权限检查）。

***

### 用户故事：把服务器自带的使用说明播报给模型（优先级：P2）

作为用户，我希望 MCP 服务器通过 `initialize` 自述的使用说明（`instructions`）在它变为可用的那一刻**播报一次**，这样"限流是多少、调用前要满足什么前提、整个服务器怎么用"这类**不调用也要知道**的内容不会丢失，同时服务器在会话中途连接时也不会让已经缓存的前缀失效。

**优先级原因**：这是上下文保真度而非工具能力。工具描述是可以被目录压缩的（模型要看原文得先搜索命中，见 `docs/specs/core/exec-tool.md`），而服务器级的使用约束一旦被压缩掉，模型就不知道自己在违反什么限制。另一面是缓存：这份内容只在"连接发生"时才有变化，若逐轮重算进系统提示，一次中途连接就会改写整个已缓存前缀（见 `docs/specs/core/prompt-cache-control.md` 边界情况 10）；作为消息追加在尾部则不改写任何已有前缀。

**独立测试**：连接一个在 `initialize` 响应里返回多行 `instructions` 的 MCP 服务器，断言对话历史里出现了这段原文并标明来源服务器，且随后几轮不再重复出现；断开后断言只播报一次"已下线"；服务器文本超过上限时断言被截断并标明。

**验收场景**：

1. **假设** 某 MCP 服务器在 `initialize` 响应里返回了 `instructions`，**当** 连接成功时，**则** `McpManager` 必须保存这段文本，不得丢弃。
2. **假设** 某服务器带有 `instructions` 且尚未播报过，**当** 它变为可用（连接成功，或 `reconnecting` 且保留上次工具快照），**则** 必须在该轮 API 调用之前追加一条持久化的系统提示消息（`isMeta`，对 UI 隐藏），内容包含该服务器原文并标明来源服务器（多台服务器时彼此可区分）。
3. **假设** `instructions` 是多行文本，**当** 播报时，**则** 必须原样保留（含换行），不得按宽度裁剪、也不得只保留首行；**但** 单台服务器的文本超过 2048 个字符时，**则** 必须截断到该上限并在末尾标明已被截断，不得静默丢弃。
4. **假设** 某服务器的工具被权限规则全部排除，**当** 计算本轮可播报集合时，**则** 不得播报该服务器的 `instructions`（说明与工具同源，不能让被拒服务器的内容绕过规则进入上下文）；服务器一个工具都没有时照常播报。规则是在播报之后才收紧时，已播报的原文按"历史不改写"留在历史里，但不得再次播报。
5. **假设** 一台已播报过的服务器断开、连接失败或转为 `error`，**当** 后续轮次计算可播报集合时，**则** 必须播报一次"该服务器已下线、上述说明不再适用"，此后不得再次播报它；不得声称会把它已经播报过的原文从历史里删除（历史不改写），也不得因此重算系统提示。
6. **假设** 本轮既没有新增也没有下线的服务器，**当** 计算可播报集合时，**则** 不得追加任何消息（无内容 = 不输出空消息、空段落或只有标题的空章节）。
7. **假设** 工具池已收敛、MCP 工具不再逐条扁平声明，**当** 服务器播报说明时，**则** 说明仍必须出现——它是服务器级上下文，不随工具声明形态变化。
8. **假设** 同一台服务器已经播报过且仍然可用、原文未变，**当** 后续每一轮到达时，**则** 不得重复播报（播报状态以对话历史中的标记为唯一真源，不另设进程内状态）。
9. **假设** 对话历史因压缩或回退导致播报标记不再存在，**当** 该服务器仍带 `instructions` 可用时，**则** 下一轮必须重新播报（宁可重复，不可丢失）。
10. **假设** 服务器断开后重新连上，**当** 它再次变为可用时，**则** 必须再播报一次它的 `instructions`（第一次播报已被"下线"公告作废，重复优于丢失）。
11. **假设** 会话中途有服务器连接或断开，**当** 对照前后两轮的系统提示文本时，**则** 系统提示（静态块与动态块）文本不得因 `instructions` 的来去而变化——工具声明随连接变化属既有行为（见 `docs/specs/core/prompt-cache-control.md` 边界情况 9）。

***

### 用户故事：管理 MCP 服务器生命周期（优先级：P2）

作为用户，我希望手动连接或断开 MCP 服务器并检查其状态，以便排查连接问题或管理资源。

**优先级原因**：提供对外部依赖的必要控制和可见性。

**独立测试**：使用 `agent.getMcpServers()` 检查状态并使用 `agent.connectMcpServer()` 重新连接失败的服务器。

**验收场景**：

1. **假设** MCP 服务器已配置，**当** 调用 `getMcpServers()` 时，**则** 它必须返回当前状态（已连接、错误等）。
2. **假设** 服务器已断开，**当** 调用 `connectMcpServer()` 时，**则** 它必须尝试重新建立连接。

***

### 用户故事：通过 Agent 构造函数传递 MCP 服务器（优先级：P2）

作为集成 SDK 的开发者，我希望直接将 MCP 服务器配置传递给 `Agent` 构造函数，以便以编程方式配置服务器而不依赖 `.mcp.json` 文件。

**优先级原因**：为 SDK 使用者启用编程式 MCP 配置。

**独立测试**：在选项中带有 `mcpServers` 创建 `Agent` 并验证服务器在没有 `.mcp.json` 文件的情况下连接。

**验收场景**：

1. **假设** `mcpServers` 传递给 `Agent.create()`，**当** 代理初始化时，**则** 这些服务器必须自动注册（并进入服务器列表），连接的时机与 `.mcp.json` 来源一致——按需拨号，创建时不拉进程。
2. **假设** 构造函数的 `mcpServers` 和 `.mcp.json` 文件同时存在，**当** 代理加载配置时，**则** 构造函数提供的服务器对于重复名称必须具有优先权。

***

### 用户故事：按需连接 MCP 服务器（优先级：P1）

作为用户，我希望 MCP 服务器在**真正要用的时候**才启动进程，而不是我每打开一个会话就为我从没用过的工具付一份常驻内存，以便长时间开着客户端也不被几十棵服务器进程拖垮（issue #2758）。

**优先级原因**：一个 stdio MCP 服务器在本机是 `uvx → uv → 服务器 → python×2` 这样一棵约 130 MB 的进程树。此前 `Agent.create` 就无条件拨号（`initializeService` 的 `autoConnect=true`），而桌面端按设计把真实会话的 Agent 池化到应用退出（切换会话不销毁），于是残留进程数 = **本次运行里创建过 Agent 的会话数**，与"现存几个会话"无关——现场实测：应用索引里 3 个会话、`wave --stdio` 子进程下挂着 11 棵树，且其中 10 棵属于已然不在会话列表里的会话。放大这一条的是**宿主形态**：桌面端 `wave --stdio` 子进程、`wave --daemon` 的长驻 `AgentBridge`、IM 桥接服务（pm2 长驻、in-process `Agent.create`、每个 IM 会话一个 Agent）都是"一个进程养很多 Agent"，后两者还没有"退出应用"这个天然重置点。再叠上销毁链路的两个失败逃逸口（引用先丢、MCP 回收排在可能抛错的步骤之后，见边界情况「销毁必须先于引用丢弃、且回收不得被前置失败阻断」），失败一次就是一棵永久孤儿树。

**独立测试**：`Agent.create()` 后断言无任何 MCP 子进程、且服务器已出现在列表中；发送第一条消息后断言进程树已拉起且本回合就能调用 MCP 工具；手动断开后再发消息断言不会被自动拉起。

**验收场景**：

1. **假设** 会话刚创建、尚未发送任何消息，**当** `Agent.create()` 完成时，**则** 不得为任何 MCP 服务器拉起子进程（`getMcpServers()` 仍必须列出全部已配置服务器，状态为未连接）。
2. **假设** 该会话第一次发送消息，**当** 回合开始时，**则** 必须在构造本轮工具配置之前完成拨号，使本轮就能列出并调用 MCP 工具（拨号与本回合其余准备工作并行，串行代价仅剩拨号本身的剩余时间）。
3. **假设** 会话已在用（服务器已连接），**当** 用户继续发消息时，**则** 不得重复拨号（已连接即复用）。
4. **假设** 某服务器按需拨号失败，**当** 本会话后续回合开始时，**则** 不得再次自动拨号（避免每轮一个超时等待、也避免进程反复拉起），状态保持错误；用户显式点「连接」才重试。
5. **假设** 用户在设置页/MCP 列表里显式「连接」某服务器，**当** 该动作完成时，**则** 它必须真的拉起进程（显式连接不受"未使用不拨号"约束）。
6. **假设** 用户显式断开了某服务器，**当** 后续回合开始时，**则** 不得因按需拨号把它重新拉起（手动断开优先，见边界情况「手动断开不得被自动重连撤销」）。
7. **假设** 会话在拨号进行中被销毁（删会话/关进程），**当** 拨号随后成功时，**则** 必须立刻关掉这条连接与其进程树，不得留下无人拥有的孤儿进程。
8. **假设** 任意服务器被断开或销毁，**当** 直接子进程（如 `uvx`）在断开后仍然存活时，**则** 必须整树终止它（Windows 用 `taskkill /PID <pid> /T /F`），不得只杀直接子进程把 `uv`/`python` 留下。
9. **假设** 某服务器从未在本机连接过（无缓存快照），**当** 打开 `/mcp` 或设置页「MCP 服务」时，**则** 该服务器必须照常出现在列表里，状态为未连接、工具列为空，可在面板内点「连接」。
10. **假设** 某服务器连接成功过，**当** 之后再次打开 `/mcp` 或设置页且它尚未拨号时，**则** 必须用上次的工具快照把工具清单展示出来，并标明来源是缓存和上次连接时间（不得把缓存冒充成"当前已连接"）。
11. **假设** 服务器配置（命令、参数、URL、传输类型、环境变量）相对缓存生成时已经变化，**当** 展示缓存时，**则** 必须判定缓存失效、不展示旧工具清单（配置变了，旧工具不再可信），直到重新拨号成功。
12. **假设** 服务器已从配置中删除，**当** 展示列表时，**则** 它的缓存条目不得出现在任何一端的列表里，并在下一次写入缓存时被清理。
13. **假设** 连接成功、工具列表刷新，**当** 该轮结束或面板收到状态变化时，**则** 必须用最新工具覆盖缓存（含清空：服务器现在没有工具就写空，不得保留旧工具）。
14. **假设** 快照缓存文件缺失、不可读或结构损坏，**当** 代理启动或打开面板时，**则** 必须静默按"无缓存"处理（列表退化成本会话真实状态），不得因此启动失败或抛错。
15. **假设** 缓存里有工具清单，**当** 构造本回合的工具配置时，**则** 不得把缓存里的工具当成可调用工具声明给模型（缓存只服务面板可见性，工具声明与调用必须以真实连接为准）。

***

### 用户故事：插件端 MCP 管理对话框（优先级：P2）

作为插件端用户，我希望通过 `/mcp` 打开 MCP 管理界面，查看各服务器的连接状态并手动连接或断开，以便在不切换到 CLI 的情况下排查连接问题。

**优先级原因**：插件端用户无法使用 CLI 的 MCP 管理界面，需要在插件内获得同等的状态可见性与基本控制能力；但属于辅助管理功能，非核心对话流程。2026-08-29 用户拍板：`/mcp` 从独立对话框改为唤起设置页并选中「MCP 服务」选项卡（与 `/agents`、`/skills` 一致）。

**独立测试**：在 IDE 中输入 `/mcp`，验证打开设置页并选中「MCP 服务」选项卡，列表列出服务器及其状态；对已断开的服务器点击连接，观察状态变为已连接；对已连接的服务器点击断开，观察状态变为未连接。

**验收场景**：

1. **假设** 用户处于图形界面三端（VS Code 扩展 / JetBrains 插件 / 桌面端），**当** 用户输入 `/mcp` 并发送，**则** 打开设置页并选中「MCP 服务」选项卡（桌面端打开全页设置，IDE 打开编辑器区域设置标签页），不再弹出独立对话框。
2. **假设** 设置页「MCP 服务」选项卡已打开，**则** 列表显示每个服务器的名称、来源（用户级 / 项目级）、连接状态（已连接/连接中/重连中/错误/未连接，以不同颜色标识）、工具数量，以及上次连接时间。
3. **假设** 某服务器处于错误状态，**当** 列表渲染时，**则** 该服务器额外显示红色错误原因，且操作按钮显示为"重连"。
4. **假设** 用户点击某服务器的"连接"或"断开"，**当** 操作进行中时，**则** 按钮显示进行中文案（"连接中..."/"断开中..."）并禁用，完成后状态刷新——断开操作必须有确定结果：即使进程无法干净退出、teardown 抛错或连接条目已缺失，host 也要回写最新服务器列表（或失败提示），按钮不得无限停留在"断开中..."。
5. **假设** 服务器状态在后台发生变化（如自动重连），**当** 列表打开时，**则** 列表实时刷新为最新状态。
6. **假设** 用户未配置任何 MCP 服务器，**当** 打开列表时，**则** 显示空状态引导文案，提示创建用户级或项目级 MCP 服务。
7. **假设** 设置页已打开，**当** 用户点击「返回」或切换其他选项卡，**则** 回到会话视图或切换到对应选项卡，设置页不产生任何消息。

***

### 用户故事：按来源 Tab 展示 MCP 服务（优先级：P1）

作为 GUI 用户，我希望设置页「MCP 服务」选项卡按来源以 Tab 形式展示 MCP 服务（用户级 / 项目级 / 插件），其中项目级在「项目级 MCP」Tab 下平铺展示（仅当前项目，不做多项目分组），以便快速定位某个来源下的 MCP 服务，并知道它属于哪一类（用户 / 项目 / 插件）。

**为什么是这个优先级**：MCP 服务来源决定其适用范围（用户级全局可用 / 项目级仅当前项目），Tab 化让长列表可扫读，与技能页形态统一（2026-08-29 用户拍板：MCP 区分用户级和项目级，用户级配置存 `~/.wave/mcp.json`，项目级仍用项目根 `.mcp.json`；2026-09-01 用户拍板：设置页只针对当前项目，删除项目分组卡片，项目 Tab 直接平铺）。

**独立测试**：在具备用户级、项目级、插件 MCP 服务的环境中打开设置页 MCP 服务选项卡，验证来源 Tab 存在、项目级服务在「项目级 MCP」Tab 平铺列出并可执行管理操作。

**验收场景**：

1. **假设** 环境中存在用户级、项目级、插件来源的 MCP 服务，**当** MCP 服务选项卡打开，**则** 顶部展示来源 Tab（用户级 MCP / 项目级 MCP / 插件 MCP），点击切换显示对应来源的服务
2. **假设** 存在项目级 MCP 服务，**当** 用户位于「项目级 MCP」Tab，**则** 服务平铺展示（不按项目分组、无项目卡片）
3. **假设** 项目 Tab 下存在多个 MCP 服务，**当** 列表展示，**则** 列出所有服务（名称、类型、命令或 URL、连接状态），并提供「编辑」「删除」操作入口
4. **假设** 用户级 MCP 配置存在，**当** 用户位于「用户级 MCP」Tab，**则** 列出 `~/.wave/mcp.json` 中定义的所有服务
5. **假设** 某来源没有任何 MCP 服务，**当** 切换到该来源 Tab，**则** 显示空状态提示，不渲染空白分组

***

### 用户故事：新建 MCP 服务（优先级：P1）

作为 GUI 用户，我希望在 MCP 服务页点击「新增 MCP 服务」后打开 AI 对话框并预填一条新建提示词，通过自然语言描述即可创建 MCP 服务；配置文件不存在时由 AI 直接创建，已存在时也交由 AI 更新——「新增」一律只预填提示词、不打开文件（与技能 / 子代理 / 钩子的新建一致）。

**为什么是这个优先级**：手写 MCP 配置 JSON 门槛高；由 AI 根据自然语言生成配置是用户需求的核心（2026-08-29 用户需求：区分用户级和项目级，没有 .mcp.json 时跳转 AI 对话框通过提示词创建；2026-09-10 用户拍板：打开文件只发生在「编辑」，新增沿用技能 / 子代理 / 钩子的「只预填」语义）。

**独立测试**：在 MCP 服务页点击「新增」，验证 AI 对话框打开并预填提示词；补充描述发送后配置被写入（用户级 `~/.wave/mcp.json` 或项目级 `.mcp.json`）；返回 MCP 服务页验证新服务出现在对应来源分组。

**验收场景**：

1. **假设** 用户在 MCP 服务页「用户级 MCP」Tab，**当** 用户点击「新增用户级 MCP 服务」，**则** 关闭设置页回到会话视图，AI 对话框打开并预填用户级提示词，如 `/settings 帮我配个用户级 MCP 服务器<名字>：连<command/url>，参数<args>`
2. **假设** 用户在「项目级 MCP」Tab 点击「新增」，**当** 输入框预填，**则** 预填项目级提示词并带上项目名，如 `/settings 帮我在【项目名称】下配 MCP 服务器<名字>：连<command/url>，参数<args>`
3. **假设** 对应配置文件（`~/.wave/mcp.json` 或项目 `.mcp.json`）尚不存在，**当** 用户补充描述后发送，**则** 由 AI 创建配置文件并写入新服务，不额外打开文件
4. **假设** 对应配置文件已存在，**当** 用户点击「新增」，**则** 仍只预填提示词、不打开文件（打开文件仅发生在「编辑」；用户可在会话中让 AI 展示当前配置）
5. **假设** MCP 服务创建成功，**当** 用户重新打开 MCP 服务页或列表刷新，**则** 新服务出现在对应来源分组（Tab）中，并带来源标识（用户 / 项目 / 插件）

***

### 用户故事：编辑 MCP 服务（优先级：P2）

作为 GUI 用户，我希望在 MCP 服务页点击「编辑」后打开 AI 对话框并预填一条编辑提示词，同时打开对应配置文件，以便基于当前内容描述修改点，由 AI 更新 MCP 服务配置。

**为什么是这个优先级**：MCP 配置修改通过 AI 自然语言描述更顺畅；同时打开配置文件让用户与 AI 都能直接看到/编辑实际 JSON（2026-08-29 用户需求：编辑跳转 AI 对话框提示词，并打开 .mcp.json 文件）。

**独立测试**：在 MCP 服务页点击某服务的「编辑」，验证 AI 对话框打开并预填编辑提示词、对应配置文件在编辑器打开；描述修改点并发送后配置文件被更新；返回 MCP 服务页验证服务配置已更新。

**验收场景**：

1. **假设** 用户在 MCP 服务页某服务条目上，**当** 用户点击「编辑」，**则** 关闭设置页回到会话视图，AI 对话框打开并预填编辑提示词，如 `帮我编辑 MCP 服务器<名字>：把<要改的内容>改成<新内容>`
2. **假设** 用户点击「编辑」，**则** 该服务所在配置文件（`~/.wave/mcp.json` 或项目 `.mcp.json`）同时打开便于对照修改（桌面端在会话视图右侧文件面板打开该文件，即消息中 read/edit/write 工具路径点击同款只读面板；IDE 在 VS Code / JetBrains 自身编辑器打开标签页）；该路径由宿主解析为**绝对路径**后随 `mcpConfigPathsResponse` 下发，webview 不自造 `~` / 相对路径——宿主按 OS 绝对路径打开文件、无法展开 `~`
3. **假设** 编辑提示词已预填，**当** 用户补充具体修改点后发送，**则** 消息以普通用户消息发送，由 AI 更新配置文件
4. **假设** MCP 服务修改成功，**当** 用户重新打开 MCP 服务页或列表刷新，**则** 服务配置反映修改后的内容

***

### 用户故事：删除 MCP 服务（优先级：P1）

作为 GUI 用户，我希望在 MCP 服务页对用户级 / 项目级 MCP 服务执行「删除」时先出现二次确认，确认后服务配置从对应配置文件中移除并从列表删除，以便清理不再使用的 MCP 服务。

**为什么是这个优先级**：删除配置不可逆，需要确认；用户需求明确「二次确认后可删」且删除不依赖 AI（2026-08-29 用户拍板：确认框 + 直接删除配置）。

**独立测试**：在 MCP 服务页点击某服务「删除」，验证出现确认对话框；确认后服务配置从配置文件中移除、列表删除该服务；取消则无任何变化。

**验收场景**：

1. **假设** 用户在 MCP 服务页某用户级 / 项目级服务条目上，**当** 用户点击「删除」，**则** 弹出二次确认对话框，说明将删除的服务名及其配置文件路径
2. **假设** 确认对话框已显示，**当** 用户点击「取消」或关闭对话框，**则** 不执行删除，服务保持原样
3. **假设** 确认对话框已显示，**当** 用户点击「确认删除」，**则** 服务配置从对应配置文件（`~/.wave/mcp.json` 或项目 `.mcp.json`）中移除，列表删除该服务，不经过 AI
4. **假设** 删除成功，**当** 用户重新打开 MCP 服务页或列表刷新，**则** 该服务不再出现在任何来源分组中
5. **假设** 服务由插件提供，**当** 列表展示，**则** 不提供「删除」入口（只读来源不可删除）

***

### 用户故事：用户级 MCP 配置（优先级：P2）

作为用户，我希望在 `~/.wave/mcp.json` 中配置常用的 MCP 服务器，以便这些服务器对所有项目可用，无需在每个项目根目录重复配置。

**优先级原因**：用户级配置是常见 MCP 服务器（数据库、代码搜索等）的复用基础，也是设置页 MCP 视图「用户级/项目级」双 tab 的数据支撑（2026-08-31 用户拍板：先支持用户级 MCP 配置，再实现 MCP 设置视图）。

**独立测试**：在 `~/.wave/mcp.json` 配置一个服务器并在无项目 `.mcp.json` 的新目录启动代理，验证服务器自动注册并可连接；同时配置用户级与项目级同名服务器，验证项目级覆盖用户级。

**验收场景**：

1. **假设** `~/.wave/mcp.json` 存在且包含 `mcpServers`，**当** 代理在任何工作目录初始化时，**则** 用户级服务器必须自动注册（进入服务器列表），但不得在初始化时拉起进程——拨号时机见「按需连接 MCP 服务器」，与项目 `.mcp.json` 一致。
2. **假设** 用户级与项目级配置了同名服务器，**当** 代理合并配置时，**则** 项目级（`.mcp.json`）覆盖用户级（`~/.wave/mcp.json`），构造函数传入的 `mcpServers` 优先级最高（构造函数 > 项目 > 用户 > 插件）。
3. **假设** 用户级 `~/.wave/mcp.json` 不存在，**当** 代理初始化时，**则** 正常继续，不报错（用户级配置为可选）。
4. **假设** 用户级 `~/.wave/mcp.json` 格式错误，**当** 代理初始化时，**则** 记录错误并跳过用户级来源，其余配置源（项目/构造函数）正常加载。

***

### 边界情况

* **按需拨号 vs 手动操作**：自动拨号只发生在"服务器从未被拨过、且没有被用户断开过"的情况下。用户显式断开的服务器在本次会话内保持断开、不再被按需拨号拉起；用户显式连接的服务器立刻拉起，不等下一次回合。没有待拨号的服务器时，回合不得为此多出等待或让出点——不用 MCP 的会话必须与没有这条机制时完全一致。
* **拨号与销毁竞态**：连接请求在飞行中时会话被销毁（删会话、切 workdir 重建、进程退出）时，`cleanup()` 只能取消定时器、看不到飞行的拨号——所以拨号成功返回后必须检查"是否已被销毁"，已销毁则立刻关掉传输与其进程树，不允许出现无人拥有的孤儿服务器进程。
* **断开后整树终止**：stdio 传输的 `close()` 只对直接子进程做 stdin EOF → SIGTERM → SIGKILL。直接子进程断开后仍存活时，必须整树终止（Windows `taskkill /PID <pid> /T /F`）；不得只杀直接子进程把 `uv`/`python` 等后代留在机器上（历史上表现为"服务器进程活到应用退出"）。
* **销毁必须先于引用丢弃、且回收不得被前置失败阻断**：`Agent.destroy()` 里 MCP 回收排在 transcript 落盘（`saveSession` / `reAppendSessionMetadata`）之后，而这两步都可能抛错（Windows 文件被占用、worktree 目录已不存在导致 ENOENT）。**transcript 落盘失败只允许记 warning 并继续 teardown**——不得让它阻断 MCP/LSP 回收；否则调用方（桌面宿主、`AgentBridge`、IM 桥接服务）都已在销毁前把引用删掉，抛错后谁也无法再找到这个 Agent，其进程树活到宿主退出为止。同理，`McpManager.cleanup()` 必须幂等：重复调用不得抛错，也不得留下半断开状态。
* **销毁请求被拒必须有兜底**：`AgentBridge` 在配置重建窗口内会以「Session X is being reconfigured」拒绝 `destroy`（重建拥有该会话条目，销毁会与重建互相打架）。调用方收到该拒绝时**不得**只丢掉自己的引用就当删完了（桌面端现在正是 `.catch(() => {})` 后照样删索引条目）：要么重试到生效，要么把该 Agent 放进可追踪的待回收队列并由宿主的后续动作清掉。沉默的「引用已删、进程还活」是本规格明确禁止的结果。
* **长驻宿主不以进程退出作为回收兜底**：`wave --daemon`（跨连接复用会话、无闲置驱逐）与 IM 桥接服务（pm2 常驻、每个 IM 会话一个 Agent）可以连续运行数周，没有"退出应用"这个天然重置点，也不存在用户手动删会话的必然动作。因此回收必须由 SDK 侧保证（按需拨号 + 销毁回收 + 销毁后不得复活），不得把责任推给宿主生命周期；IM 桥接服务通过 npm 依赖 `wave-agent-sdk` 接入，修复随 SDK 版本生效（它 pin 了具体版本，需要升依赖），本次不修改该仓库。
* **未拨号时的可见性（工具快照缓存）**：按需拨号后，"什么都没连"不能等于"什么都看不见"。首次连接成功时把该服务器的工具清单写进本机快照缓存（`~/.wave/mcp-tools-cache.json`，键 = 服务器名 + 配置指纹），面板在未拨号时用缓存展示工具并标明"来自缓存 / 上次连接时间"，状态仍是未连接；配置指纹变化即视为失效，不展示旧工具。缓存只服务面板可见性——**不得**作为模型侧的工具声明来源（否则模型会拿到已经不存在的工具的调用权）。缓存不可读/损坏按"无缓存"处理，不阻塞启动。清理只按写入方自己的项目范围做：一个进程同时托管多个会话（桌面 `wave --stdio`、daemon），写缓存时只清理"同 workdir 且已不在配置里"的条目，不得因为 B 项目的写入而删掉 A 项目的快照。
* **手动断开不得被自动重连撤销**：用户主动断开某服务器时，teardown 触发的 transport close 不得被指数退避自动重连机制再次拉起；断开后需用户显式点击"连接"才会重连。自动重连仅适用于非人为原因（进程崩溃、传输意外断开）的恢复。
* **断开 vs 删除**：断开仅是运行时动作——终止子进程并把状态置为未连接，配置文件（`.mcp.json` / `~/.wave/mcp.json`）中的服务保留，可随时手动重新连接；从配置中移除服务须走"删除"。
* **断开竞态护栏**：断开时若有并发连接/崩溃后的陈旧进程退出事件，不得误删新建立的连接或误改新状态——只有"当前代际"的传输关闭才允许改写该服务器的连接与状态。
* **服务器崩溃**：如果 MCP 服务器进程意外终止，`McpManager` 必须检测传输错误并将服务器状态更新为 `error`。
* **工具名称冲突**：来自不同服务器的同名工具通过添加 `mcp__[serverName]__[toolName]` 前缀来处理。该扁平全名同时是沙箱 `tools` 对象上的键与权限规则匹配的身份；工具池收敛只撤下"声明"，全名与身份都不变（见 `docs/specs/core/exec-tool.md`）。
* **不支持的 Schema 字段**：包含 `$schema`、`exclusiveMinimum` 或 `exclusiveMaximum` 等字段的 MCP 工具 schema 必须被清理以确保与 LLM API 的兼容性。
* **服务器级 `instructions` 与工具描述的职责边界？** 服务器级的 `instructions` 走独立的播报通道（服务器可用时追加一条 `isMeta` 消息，见本文件"把服务器自带的使用说明播报给模型"）、不参与目录预算也不裁剪，适合承载"不调用也要知道"的内容（限流、前置条件、整体用法约定）；单个工具的描述会被目录压缩、要拿原文得靠搜索命中，所以不要把服务器级约束写进工具描述里。
* **`instructions` 会不会让前缀缓存失效？** 不会（这正是把它从系统提示挪到消息通道的原因）：播报只追加在消息尾部，不改写系统提示文本，也不改写任何已有历史。对齐对象是 Claude Code 的 `mcp_instructions_delta` 附件——CC 把同一条内容从"每轮重算的系统提示段"移出，注释写明后者 "busts the prompt cache on late MCP connect"。代价是两点，都是刻意的：①服务器断开**不能**再把原文从上下文里移除（历史不改写，只能追加一条"不再适用"）；②断开后重连会再播报一次（重复优于丢失）。单台服务器文本上限 2048 **字符**（对齐 Claude Code 的 `MAX_MCP_DESCRIPTION_LENGTH`），按英文字符口径计价、不做 CJK 感知（MCP 服务器自述的说明绝大多数是英文，与目录预算同一口径）。
* **无效配置**：如果 `.mcp.json` 格式错误，代理应记录错误并在没有 MCP 工具的情况下继续。
* **配置合并**：当存在多个配置源（构造函数、项目 `.mcp.json`、用户 `~/.wave/mcp.json`、插件）时，它们按以下优先级合并：构造函数 > 项目（.mcp.json）> 用户（~/.wave/mcp.json）> 插件服务器。
* **SSE 重连**：当 SSE EventSource 连接意外断开时，系统必须尝试使用指数退避自动重连。重连期间，服务器状态必须显示 "reconnecting"。如果所有尝试后重连仍失败，状态必须转为 "error"。
* **无 HTTP→SSE 回退**：当基于 URL 的服务器具有 `type: "http"`（或无 type，默认为 `"http"`）时，如果 Streamable HTTP 连接失败，系统不得回退到 SSE。需要 SSE 的用户必须显式设置 `type: "sse"`。
* **未知类型**：如果 `type` 设置为无法识别的值，系统必须在连接时抛出错误。
* **IDE 对话框状态刷新竞态**：插件端对话框收到状态推送时，必须忽略比当前列表更旧的快照，防止旧数据覆盖新状态。
* **删除 MCP 服务失败怎么办？** host 更新配置文件失败（权限不足 / 文件不存在）时，向 webview 返回错误提示，列表保持删除前状态，不静默失败
* **用户级 / 项目级 MCP 配置文件路径？** 用户级 `~/.wave/mcp.json`，项目级 `<项目工作目录>/.mcp.json`；两级合并加载，同名服务器项目级覆盖用户级（合并顺序为 插件 → 用户 → 项目 → 构造函数，后合并的覆盖先合并的，见上文"配置合并"）
* **项目级 MCP 的目标项目是哪个？** 三端设置页均为单项目模型——展示/管理当前会话 workdir 的项目级配置；「项目级 MCP」Tab 平铺展示，无项目分组归属推断（2026-09-01 用户拍板：设置页只针对当前项目）
* **项目级 MCP 的目标项目是哪个？** 三端设置页均为单项目模型——展示/管理当前会话 workdir 的项目级配置；新建/编辑项目级 MCP 的目标项目 = 当前对话的 workdir，AI 对话框直接写入该项目目录（2026-08-29 用户拍板：不做跨项目选择）
* **新建/编辑 MCP 提示词中的 `/settings` 前缀怎么办？** GUI 中 `/settings` 不是可拦截的本地斜杠命令，预填文本作为普通用户消息原样发送给 AI；前缀仅作为自然语言指令的一部分提示 AI 处理 MCP 配置请求
* **IDE 侧「打开配置文件」与「关闭设置页」的先后顺序？** 两个动作都由设置页入口向宿主发出，且关闭动作会销毁设置页载体（VS Code 设置 WebviewPanel 被 dispose、JetBrains 设置文件编辑器被 close）；**打开文件必须先于关闭设置页发出**，否则销毁后到达的打开请求被丢弃——表现为四个视图（技能 / 子代理 / 钩子 / MCP 服务）点「编辑」都不打开文件（桌面端设置页与聊天共用一个 webview，不涉及此顺序）。同理，配置文件路径缺失时只预填提示词、不打开文件（不自造占位路径）

## 假设

* MCP 服务器可以是本地进程（stdio）或远程端点（http/sse）。
* 代理具有执行 MCP 配置（`.mcp.json` / `~/.wave/mcp.json`）中指定命令的足够权限。
* `.mcp.json` 文件位于代理的工作目录中，用户级 `~/.wave/mcp.json` 对所有工作目录生效。
* MCP 服务器配置可以通过多个源提供：构造函数选项、项目 `.mcp.json`、用户 `~/.wave/mcp.json`、插件配置。
* 工具快照缓存是**本机、跨会话**的展示用缓存（`~/.wave/mcp-tools-cache.json`），与配置文件同一隐私边界，不含任何凭据；同一台服务器在不同 workdir 下的配置指纹不同，互不覆盖。
* 按需拨号把"会话没被用过就不拉进程"当作默认：代价是第一个真正用到 MCP 的回合要等拨号完成（拨号与本回合其它准备并行），且未拨号前面板只能展示缓存或空工具列表。
