# 项目总览(popup-mcp)
本项目是基于 Vite + Vue 前端 和 Tauri 后端 的桌面应用,用于通过 MCP(Model Context Protocol)向用户提出选择题与展示确认对话框,并在工具调用时弹出交互窗口。
文档目标:在 1–2 分钟内帮助开发者建立整体认知,快速定位关键模块和代码入口。
---
## 1. 技术栈与整体架构
> 交互协议、Schema 及扩展步骤的详细说明见 `docs/interaction.md`。
- 前端:
- 基于 Vite 的单页应用(`src/`、`public/` 等),负责渲染主窗口 UI。
- 通过统一事件通道 `ui:request` 接收后端发来的交互请求,在同一窗口内展示不同类型的对话框组件,并在用户操作后调用 Tauri 命令 `answer_interaction` 回传结果。
- 对话框渲染采用注册式 `dialogRegistry` + `DialogHost`,按窗口 label 分队列(Map<label, Queue>),队列为空时自动隐藏对应窗口,新增对话框仅需注册解码与组件映射。
- 后端(Tauri):
- 代码位于 `src-tauri/`,使用 Rust 实现。
- 通过 `rmcp` crate 实现 MCP server,暴露工具 `ask-user-question`、`confirm-action`,供 MCP host 调用。
- 使用 Tauri 提供的 `AppHandle` 与前端窗口通信,事件统一为 `ui:request`,命令统一为 `answer_interaction`。
- 窗口管理:`WindowManager` 基于模板表(label、decorations、透明度、默认尺寸)按 `uiHints.window` 选择/创建窗口;当前仅保留单一模板 `main`,两种工具(ask/confirm)统一复用同一窗口,按 `layout` 区分持久化几何。
- 几何持久化采用 **窗口 label + layout profile** 组合键:`window_state.rs` 读取/写入 `~/.popup-mcp/window-state.json` v2,UI 管理层在弹窗前应用持久化几何,前端在关闭时回传保存。
- 启动参数:`--ui-lang`(强制 UI 语言)、`--ui-timeout-seconds`(UI 等待超时,秒)、`--header-text`(覆盖顶部工具名,空或仅空格回退默认)。
- MCP 集成:
- 使用 `rmcp = 0.8.5`(启用 `server`、`transport-io` 特性)作为 Rust 版 MCP SDK。
- Server 通过标准输入/输出(stdio)与 MCP host 通信。
- 当 stdio 连接关闭(例如 MCP host / `mcp-proxy` 退出)时,进程会自动退出,避免残留后台进程。
- UI 层使用 `vue-i18n` 做中英文本地化,语言优先级为:启动参数 `--ui-lang`(`zh`/`zh-CN`/`en`/`en-US`) → 前端根据系统语言(`navigator.language`)自动判定(以 `zh` 开头视为中文,其余视为英文)。
高层调用链(从外到内):
MCP Host → MCP server 工具(`ask-user-question` / `confirm-action`)→ Tauri 后端(spawn 的 stdio server)→ 发送事件到前端窗口 → 用户操作后通过 Tauri 命令回传 → MCP 工具返回结构化结果。
---
## 2. 目录结构速览
仓库根目录关键结构:
- `src/`:前端源代码(Vite + Vue),负责渲染主窗口 UI 和问答交互。
- `public/`:静态资源。
- `src-tauri/`:Tauri 后端及 MCP server 实现,是本项目的 Rust 部分核心。
- `Cargo.toml`:后端 Rust 工程配置(依赖 `rmcp`、`tauri` 等)。
- `src/mcp/`:MCP server 与 UI 桥接的核心模块目录(`types.rs`、`server.rs`、`ui_bridge.rs`、`ui_runtime.rs`、`commands.rs` 等)。
- `src/main.rs`(如存在):Tauri 入口,注册命令、启动应用窗口和相关插件。
- `capabilities/`:Tauri 权限与窗口能力配置(如 `default.json`)。
- `tauri.conf.json`:Tauri 应用配置(窗口、打包等)。
- `.github/workflows/release.yml`:多平台 Release 工作流,在 4 个平台构建裸二进制,并同时上传裸二进制与压缩包(Windows `.zip`、macOS/Linux `.tar.gz`)到 GitHub Release 资产。
前端与后端之间通过 Tauri 事件和命令进行通信,后端与 MCP host 则通过 `rmcp` 标准输入/输出通道连接。
---
## 3. MCP server 与工具实现定位
核心模块目录:`src-tauri/src/mcp/`
主要职责:
1. **UI 请求数据结构**
- `InteractionPayload`:统一的前端请求载荷,包含 `id`、`kind`、`request`(透传每个工具的 UI 参数)与可选 `ui_hints`。
- `InteractionUiHints`:当前支持 `window`(窗口模板/label)与 `layout`(布局 profile,用于尺寸持久化、默认大小选择);未传时默认按工具注册表选择窗口、按 `kind` 选择 layout。
- `AskUserQuestionUiPayload` / `ConfirmDialogUiPayload`:仍是工具侧的具体 UI 参数结构,作为 `request` 嵌入统一通道中。
- `Question` / `QuestionOption`:问卷问题与选项定义。
- `AskUserQuestionAnswer` / `ConfirmActionResult`:UI 层返回给工具的结构化结果(分别对应问卷与确认对话框)。
- 其中 `AskUserQuestionAnswer` 现在包含:`answers: string[]`(元素形如 `question → label`,多选用 `, ` 分隔多个 label,用户自填“其他/Other”时直接为用户输入文本,不添加 `Other -` 前缀)与可选的 `extraInfo?: string`,用于承载用户在提交界面填写的全局额外说明。
- 当用户在问卷界面主动取消时,ask-user-question 工具返回结构为:`{ status: "cancelled", cancelReason?: string | null }`(视为正常路径,不再带 `errorMessage` 字段),其中 `cancelReason` 为用户在取消弹窗中填写的原因(可为空)。
- `UiResponseStatus` / `UiResponse` / `UiRequest`:表示 UI 请求/响应的状态和数据枚举,`UiRequest` 现为单一变体 `Interaction`(统一事件),`UiBridge` 仍基于 `oneshot` 通道桥接。
- `UiBus`:新的后端总线,负责生成请求 ID、发送统一交互事件、处理超时与响应。
- `ToolRegistry` + `UiTool` trait:声明工具元数据(kind、窗口标识),供窗口几何等后续扩展。
2. **MCP server 服务结构:`AskUserService`**
- 通过 `#[tool_router]` 与 `ToolRouter<AskUserService>` 组合,自动收集带 `#[tool]` 标记的方法作为 MCP 工具。
- 字段:
- `tool_router`:`rmcp` 的工具路由器。
- `ui_bus`:封装统一交互通道、超时与一次性响应的发送/等待逻辑。
3. **工具实现:`ask-user-question` / `confirm-action`**
- `ask-user-question`:
- 入口签名:`pub async fn ask_user_question(&self, Parameters(params): Parameters<AskUserQuestionParams>) -> Result<CallToolResult, McpError>`。
- 参数 `AskUserQuestionParams` 包含 `questions` 数组(至少 1 个问题,问题数量不限,每个问题至少 2 个选项且无上限),以及必填 `topic` 字段,用于在弹窗顶栏显示本次交互的主题,便于在多 Agent 并行时区分来源。
- 调用流程:
1. 验证参数(问题数量、选项数量、必填字段)。
2. 使用 `UiBus::dispatch` 生成请求 ID,注册 `oneshot` 通道,并将 `AskUserQuestionUiPayload` 封装到 `InteractionPayload` 后发送。
3. 等待 UI 回传结果(可选通过启动参数 `--ui-timeout-seconds N` 设置全局超时,单位为秒,N > 0 时生效)。
4. 根据 `UiResponseStatus` 返回答案对象或结构化错误信息,其中成功结果为 `{ answers: string[], extraInfo?: string }`,取消场景下返回 `{ status: "cancelled", cancelReason?: string | null }`(不再包含 `errorMessage`)。
- `confirm-action`:
- 用于展示确认对话框,参数包含标题、正文、按钮文案、是否危险操作、默认确认键等;支持必填 `topic` 字段用于在顶栏显示本次交互主题;返回 `{ confirmed: boolean }`。
- 通过统一交互事件发送 `ConfirmDialogUiPayload`,由前端展示玻璃态确认卡片。
4. **Server 信息与能力声明**
- 通过 `#[tool_handler]` 实现 `rmcp::handler::server::ServerHandler`:
- `get_info` 返回 `ServerInfo`:
- `instructions`:简要中文说明本 MCP server 的用途("通过 Tauri 窗口向用户提出选择题的 MCP server,支持多问题、单选/多选模式。")。
- `capabilities`:`ServerCapabilities::builder().enable_tools().build()`,声明支持工具调用。
- 其余字段使用 `Default::default()`,由 `rmcp` 根据构建环境填充协议版本、实现信息等。
5. **启动与 UI 管理**
- `spawn_mcp_and_ui(app_handle: AppHandle, bridge: UiBridge, ui_timeout: Option<Duration>)`:在 Tauri 启动时被调用,用于并行启动:
- MCP server(基于 `stdio` 的 `AskUserService::new(ui_bus).serve(stdio())`,UI 超时时间由 Tauri 启动参数 `--ui-timeout-seconds` 解析后传入)。
- UI 管理任务 `run_ui_manager`:
- 从 `ui_rx` 通道接收 `UiRequest`。
- 查找标签为 `"main"` 的窗口,根据 `kind` 选择窗口几何并发送统一事件 `"ui:request"`,确保窗口可见和聚焦。
- 如果找不到 `main` 窗口或向前端发送事件失败,会通过 `UiBridge` 立即向该请求返回 `status: "error"` 的结构化结果,避免工具调用长时间挂起。
6. **前端回传命令**
- `#[tauri::command] pub async fn answer_interaction(...)`:
- 前端在用户完成选择后调用该命令,将 `request_id`、`status`(`"ok"`/`"cancelled"`/`"timeout"`/`"error"`)、`answers`(JSON 字符串)与 `error_message` 回传。
- 命令内部通过 `UiBridge` 找到对应的 `oneshot` 发送端并发送 `UiResponse`,唤醒 MCP 工具调用端。
---
## 4. 典型调用路径示意
1. MCP host 调用工具 `ask-user-question`,并传入参数(questions 数组)。
2. `AskUserService::ask_user_question` 使用 `UiBus` 发送统一交互事件 `ui:request`(`kind = ask-user-question`,`request` 为 `AskUserQuestionUiPayload`)。
3. UI 管理任务将事件转发给前端,前端弹出问答对话框。
4. 用户完成操作后,前端调用 Tauri 命令 `answer_interaction` 回传结果。
5. `answer_interaction` 通过 `UiBridge` 唤醒对应的 MCP 工具调用,将结果封装为 `CallToolResult` 返回给 MCP host。
---
## 5. 前端 UI 特性
前端实现(入口:`src/App.vue`)采用 **macOS 风格**的毛玻璃设计,提供精美现代化的问答交互界面;全局主题与布局样式集中在 `src/styles/base.css`。对话框 UI 已拆分:
- 问卷:`src/components/AskUserQuestionDialog.vue` 仅负责编排,具体视图拆到 `src/components/ask/`(顶部进度、面包屑、题目卡、提交汇总、快捷键栏、取消弹层等),并复用组合式逻辑 `src/composables/ask/`(选项状态、导航、键盘快捷键)。
- 确认:使用共享容器 `src/components/common/DialogShell.vue`,保留独立的内容与键盘交互。
### 设计风格
- **macOS 自适应风格**:采用 macOS Big Sur/Ventura 风格的深色模式设计,中性色调背景
- **无边框透明窗口**:主窗口使用 Tauri 无边框 + 透明背景配置,整体以悬浮玻璃态形式显示在桌面上,顶部栏区域可拖动移动窗口
- **macOS 透明实现约定**:macOS 下透明窗口依赖 Tauri 的 `macos-private-api` 特性(使用 macOS 私有 API),因此不支持发布到 Mac App Store。
- **毛玻璃(Glassmorphism)美学**:半透明磨砂玻璃卡片效果,配合 backdrop-filter 实现中等强度模糊(blur 20-30px)
- **系统蓝色主题**:
- 主色:macOS 系统蓝 (#007AFF → #0A84FF)
- 成功色:系统绿 (#34C759 → #30D158)
- 警告色:系统橙 (#FF9500 → #FF9F0A)
- 错误色:系统红 (#FF3B30 → #FF453A)
- **纯净背景**:移除浮动渐变光球装饰,使用简洁的中性深灰背景 (rgba(28, 28, 30, 0.98))
### 核心功能
- **多问题支持**:一次可显示多个问题(数量不限),每个问题独立管理状态
- **选项式交互**:每个问题提供至少 2 个预设选项(无上限),每个选项有 label 和 description
- **单选/多选模式**:根据问题的 `multiSelect` 字段动态切换,带图标标识(◉ 单选/☑ 多选)
- **"其他"选项**:自动为每个问题添加"其他"选项,允许用户输入自定义答案
- **请求队列**:前端通过注册式 `dialogRegistry` + `DialogHost` + `useUiDialog` 维护窗口级队列(Map<label, Queue>,FIFO),ask/confirm 作为注册项解码后入队;同一时间仅展示当前窗口的一个对话框,队列耗尽后自动隐藏窗口,顶部栏显示当前请求以及排队概要。
### 动画效果
- **页面切换动画**:Vue Transition 实现问题间的滑动切换(向左/向右)
- **微交互动画**:
- 选项 hover 上浮效果(translateY -1px)
- 选项选中时的系统蓝外发光效果
- 焦点状态的系统蓝描边效果(box-shadow)
- 按钮和卡片的平滑过渡(0.2s cubic-bezier(0.4, 0, 0.2, 1))
- **加载动画**:
- 顶部进度条的闪光流动效果
- 面包屑图标的旋转动画
- 错误提示的震动动画
- **渐进式展现**:选项列表的交错淡入(fadeInUp + stagger delay)
### 图标系统
- **状态图标**:✓ 已完成 / ○ 未开始
- **问题类型图标**:◉ 单选 / ☑ 多选
- **装饰图标**:◈ 应用标识 / ⚠ 错误提示
### 进度可视化
- **顶部进度条**:渐变色进度条 + 闪光流动动画
- **面包屑导航**:
- 显示所有问题步骤 + 提交步骤
- 状态图标指示(已完成/当前/未开始)
- 支持点击跳转到指定问题
### 交互体验
- **键盘导航**:完整的键盘快捷键支持(方向键、Enter、数字键等)
- **macOS 风格卡片**:选项和对话框使用毛玻璃效果和统一的 12-16px 圆角
- **柔和阴影**:使用 macOS 风格的柔和分层阴影,增加卡片的空间深度感
- **系统蓝焦点状态**:焦点元素使用系统蓝色外发光效果(box-shadow)
- **答案验证**:提交前检查每个问题是否都有答案
- **状态管理**:使用 Map 管理每个问题的选中状态和"其他"输入
- **响应式设计**:适配不同屏幕尺寸,移动端友好
此外:
- 确认对话框(Confirm):在主窗口内容区域内居中展示,加强关键操作确认的视觉聚焦
---
## 6. 后续扩展建议(方向性说明)
- 如需新增 MCP 工具(例如确认弹窗、进度提示等),推荐复用当前模式:
- 在 `src-tauri/src/mcp/` 中为每类交互定义参数结构体(使用 `Parameters<...>`),并通过 `#[tool]` 标记导出。
- 在 UI 管理任务中新增事件分发逻辑,与前端约定不同事件名和负载结构。
- 在 `UiRequest` enum 中添加新的变体。
- 当 MCP 端能力增加时(如 resources/prompts),可在 `ServerCapabilities::builder()` 上逐步启用相应能力,并在本 OVERVIEW 中补充相应模块说明。
---
## 7. 分发与发布说明
### 版本管理
- **统一版本号**:`package.json`、`src-tauri/tauri.conf.json`、`src-tauri/Cargo.toml` 三个文件的版本号保持一致
- **版本同步**:使用 `scripts/sync-version.js` 脚本自动同步版本号,执行 `pnpm version <patch|minor|major>` 时会自动运行
### GitHub Releases 二进制分发
- **分发方式**:从 GitHub Releases 下载对应平台的压缩包(推荐)或裸二进制,然后运行(`popup-mcp` / `popup-mcp.exe`)。
### GitHub Release 自动构建
- **触发方式**:推送 `v*.*.*` 格式的 git 标签
- **构建矩阵**(4 个平台并行):
1. macOS x64 (`x86_64-apple-darwin`) on `macos-latest`
2. macOS arm64 (`aarch64-apple-darwin`) on `macos-latest`
3. Windows x64 (`x86_64-pc-windows-msvc`) on `windows-latest`
4. Linux x64 (`x86_64-unknown-linux-gnu`) on `ubuntu-latest`
- **构建产物**:执行 `pnpm tauri build --no-bundle --target <triple>` 生成裸二进制,直接上传:
- 裸二进制:
- `popup-mcp-v<版本>-macos-x64`
- `popup-mcp-v<版本>-macos-arm64`
- `popup-mcp-v<版本>-windows-x86_64.exe`
- `popup-mcp-v<版本>-linux-x86_64`
- 压缩包:
- `popup-mcp-v<版本>-macos-x64.tar.gz`
- `popup-mcp-v<版本>-macos-arm64.tar.gz`
- `popup-mcp-v<版本>-windows-x86_64.zip`
- `popup-mcp-v<版本>-linux-x86_64.tar.gz`
> 说明:当前使用 `pnpm tauri build --no-bundle` 来构建裸二进制。
### 发布流程
1. **更新版本**:
```bash
pnpm version patch # 或 minor/major
# 自动执行版本同步,更新三个文件的版本号
```
2. **提交变更**:
```bash
git commit -m "chore: release v0.x.x"
```
3. **推送标签**:
```bash
git push origin main --tags
```
4. **自动构建与发布**:
- GitHub Actions 在 4 个平台并行构建
- 创建 GitHub Release 并上传裸二进制与压缩包资产
### 配置要求
- **权限配置**:workflow 已配置 `contents: write` 权限用于创建 Release