diff --git a/crates/host-core/src/permissions.rs b/crates/host-core/src/permissions.rs index b3c0798248..10cb225a1e 100644 --- a/crates/host-core/src/permissions.rs +++ b/crates/host-core/src/permissions.rs @@ -129,7 +129,10 @@ impl PermissionManager { // low-risk grant. Medium preserves the normal approval path. _ => Risk::Medium, }, - name if name.starts_with("mcp_") => Risk::Low, + // MCP servers are user-configured but their tools are opaque; a + // self-declared annotation comes from the server, so it is not + // trusted and never lowers the approval path. + name if name.starts_with("mcp_") => Risk::Medium, _ => Risk::Medium, } } @@ -695,6 +698,96 @@ mod tests { assert_eq!(d, Some(PermissionDecision::AllowSession)); } + #[test] + fn mcp_tools_default_to_medium_and_ignore_declared_risk() { + for declared in [ + None, + Some("low"), + Some("medium"), + Some("high"), + Some("bogus"), + ] { + assert!( + matches!( + PermissionManager::tool_risk_with_declared("mcp_srv_tool", declared), + Risk::Medium + ), + "mcp tool with declared {declared:?} must be medium" + ); + } + } + + #[test] + fn mcp_tools_prompt_under_ask_and_accept_edits() { + let pm = PermissionManager::default(); + for mode in ["ask", "accept-edits"] { + for declared in [None, Some("low")] { + let d = + pm.evaluate_auto_with_permission_mode_and_risk(PermissionEvaluationParams { + session_id: "s", + tool_name: "mcp_srv_tool", + mode: "agent", + permission_mode: mode, + session_grants: &no_grants(), + declared_risk: declared, + requires_external_path_permission: false, + plan_safe_actions: None, + }); + assert!( + d.is_none(), + "mcp tool must prompt under {mode} ({declared:?})" + ); + } + } + } + + #[test] + fn mcp_tools_auto_allow_under_auto() { + let pm = PermissionManager::default(); + let d = pm.evaluate_auto_with_permission_mode( + "s", + "mcp_srv_tool", + "agent", + "auto", + &no_grants(), + ); + assert_eq!(d, Some(PermissionDecision::AllowOnce)); + } + + #[test] + fn mcp_session_grant_skips_prompt() { + let pm = PermissionManager::default(); + let mut grants = HashMap::new(); + grants.insert("s".to_string(), vec!["mcp_srv_tool".to_string()]); + let d = pm.evaluate_auto_with_permission_mode("s", "mcp_srv_tool", "agent", "ask", &grants); + assert_eq!(d, Some(PermissionDecision::AllowSession)); + let other = + pm.evaluate_auto_with_permission_mode("s", "mcp_srv_other", "agent", "ask", &grants); + assert!(other.is_none(), "grant is scoped to the exact tool name"); + } + + #[test] + fn mcp_tools_denied_in_contract_modes() { + let pm = PermissionManager::default(); + let mut grants = HashMap::new(); + grants.insert("s".to_string(), vec!["mcp_srv_tool".to_string()]); + for contract in ["plan", "goal"] { + for mode in ["ask", "accept-edits", "auto"] { + assert_eq!( + pm.evaluate_auto_with_permission_mode( + "s", + "mcp_srv_tool", + contract, + mode, + &grants + ), + Some(PermissionDecision::Deny), + "mcp tool must be denied in {contract} + {mode}" + ); + } + } + } + #[test] fn contract_mode_admits_plugin_tools_only_with_plan_safe_actions() { let pm = PermissionManager::default(); diff --git a/crates/host-core/src/tools/mod.rs b/crates/host-core/src/tools/mod.rs index 62ee0dd33a..d1246111e5 100644 --- a/crates/host-core/src/tools/mod.rs +++ b/crates/host-core/src/tools/mod.rs @@ -954,9 +954,10 @@ fn count_lines_fast(path: &Path) -> std::io::Result { /// user's MCP servers both live in Electron main, so both are forwarded over /// `plugins.execute` instead of being executed here. /// -/// `mcp_` is treated exactly like `plugin_` for risk and read-only-mode -/// purposes: the user typed the command or URL into the MCP editor themselves, -/// which is at least as deliberate as accepting a plugin's manifest. +/// `mcp_` is treated like `plugin_` for dispatch and read-only-mode purposes. +/// For risk it matches a plugin tool without a valid declaration (`medium`): +/// the user configured the server, but its tools and any risk they self-declare +/// are opaque, so they keep the normal approval path (`permissions.rs`). pub fn is_desktop_dispatched(tool_name: &str) -> bool { tool_name.starts_with("plugin_") || tool_name.starts_with("mcp_") } diff --git a/docs/adr/README.md b/docs/adr/README.md index 1fb6ada89c..4bac9199ab 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -20,6 +20,7 @@ Each ADR includes: | ID | Title | Status | |---|---|---| +| mcp-tool-approval-risk | [User MCP tools keep the normal approval path](mcp-tool-approval-risk.md) | Accepted | | models-dev-catalog-authority | [models.dev owns published model metadata](models-dev-catalog-authority.md) | Accepted for implementation | | pi-ai-core-0991-authority | [Pi 0.99.1 account model authority](pi-ai-core-0991-authority.md) | Superseded for chat model metadata | | plan-tool-declarations-and-execution-denials | [Keep known tool declarations while denying contract-mode execution](plan-tool-declarations-and-execution-denials.md) | Accepted for implementation | diff --git a/docs/adr/mcp-tool-approval-risk.md b/docs/adr/mcp-tool-approval-risk.md new file mode 100644 index 0000000000..ae1cb773cc --- /dev/null +++ b/docs/adr/mcp-tool-approval-risk.md @@ -0,0 +1,40 @@ +# ADR: User MCP tools keep the normal approval path + +- Status: Accepted +- Date: 2026-10-03 +- Decision: D640 + +## Context + +User-configured MCP servers expose tools under the `mcp__` +namespace (ADR 0056). `PermissionManager` classified every `mcp_` tool as +`low` risk, which auto-allows in every mode, so under `ask` a server's tools +could write files, reach the network or run commands without an approval card. +The user chose to launch the server, but its tool list and behavior come from +the server and can change between versions. Servers may also annotate their +own tools as read-only or low risk; that claim comes from the party being +gated. + +## Decision + +Classify `mcp_` tools as `medium` risk, the same as a plugin tool without a +valid declaration. Under `ask` and `accept-edits` each call shows the approval +card with the reason "MCP server tool requires approval"; allow-once covers +one call and allow-session covers that exact tool name for the session. `auto` +runs the tool without a card, and Plan/Goal keep denying it. Server-declared +risk or annotations are ignored and never lower the path. + +Dispatch over `plugins.execute`, read-only-mode handling and the `mcp_` +namespace are unchanged. No host protocol, schema or persistence change. + +## Consequences + +Users see an approval card for MCP tool calls in `ask` and `accept-edits` +until they grant the tool for the session. A per-server or per-tool persistent +allowlist, if wanted later, is a separate decision. + +## Alternatives + +Trusting server annotations would let a server mark itself safe. Using `high` +risk would add friction without a different settlement path, since `medium` +already prompts in every non-`auto` mode. diff --git a/docs/spec/03-runtime/03-tools-and-permissions.md b/docs/spec/03-runtime/03-tools-and-permissions.md index f693070d99..867266ec22 100644 --- a/docs/spec/03-runtime/03-tools-and-permissions.md +++ b/docs/spec/03-runtime/03-tools-and-permissions.md @@ -400,6 +400,15 @@ Initial denylist (extensible): | medium | low-risk network/metadata | Confirm or allow by policy | | high | Write/Edit/Bash | Confirm by default | +Tools from user-configured MCP servers (`mcp__`) are classified +`medium`, the same as a plugin tool without a valid declared risk. A risk level +self-declared by an MCP server is not trusted, unlike the risk in a plugin +manifest the user accepted. Under `ask` and `accept-edits` an MCP tool call +shows an approval card with reason "MCP server tool requires approval"; an +`allow-session` grant suppresses further prompts for that tool name in that +session (grants are in-memory only). `auto` auto-allows it, and the Plan/Goal +contract-mode hard deny still applies (D640, ADR `mcp-tool-approval-risk`). + ### Decision Types - `allow-once` diff --git a/docs/spec/05-security/01-security.md b/docs/spec/05-security/01-security.md index 9a8eb58edc..f08f8572ed 100644 --- a/docs/spec/05-security/01-security.md +++ b/docs/spec/05-security/01-security.md @@ -96,6 +96,13 @@ and byte size, and only then creates the `plan_approvals` record with structured title/question fields. Renderer and sidecar state cannot write or replace an artifact. +Tools from user-configured MCP servers (`mcp__`) are never +low-risk by default: host-core classifies them `medium` and ignores any risk +level the MCP server declares for itself. `ask` and `accept-edits` require +approval (an `allow-session` grant covers the same tool name for the rest of +that session, in memory only), `auto` auto-allows, and Plan/Goal still deny +them (D640, ADR `mcp-tool-approval-risk`). + ## 4.1 Skill market egress The renderer does not fetch skill catalogs or SKILL.md documents. Electron diff --git a/docs/spec/06-delivery/04-e2e-test-plan.md b/docs/spec/06-delivery/04-e2e-test-plan.md index c53ac68526..44c0ff336f 100644 --- a/docs/spec/06-delivery/04-e2e-test-plan.md +++ b/docs/spec/06-delivery/04-e2e-test-plan.md @@ -3315,6 +3315,28 @@ identify the platform validation still needed. `07-plugins/04-plugin-security.md` §8.1 - **Status**: Client and session-isolation unit-covered; full desktop journey Draft +#### E2E-MCP-tool-requires-approval: User MCP tools prompt under ask and accept-edits + +- **Preconditions**: A project-bound Agent session; one user-configured stdio + MCP server whose tool list annotates a tool as read-only/low risk. +- **Steps**: 1) With the session in `ask`, ask the agent to call the MCP tool. + 2) Answer the card with allow-once, call it again, then answer with + allow-session and call it a third time. 3) Switch to `accept-edits` in a new + session and repeat the call. 4) Switch to `auto` and call it. 5) Switch to + Plan, then Goal, and call it. +- **Expected**: Under `ask` and `accept-edits` every call shows an approval card + with reason "MCP server tool requires approval" at `medium` risk, regardless of + the server's self-declared annotation. Allow-once covers only that call; + allow-session suppresses further cards for the same `mcp__` + name in that session only, and does not cover other tools of the server. + `auto` runs the tool without a card. Plan and Goal deny it even with a + session grant. +- **Specs linked**: `03-runtime/03-tools-and-permissions.md`, + `05-security/01-security.md`, D640, ADR `mcp-tool-approval-risk` +- **Acceptance**: E (tools & permissions) + Security +- **Status**: Unit-covered (host-core `permissions.rs` MCP risk and mode tests); + desktop journey Draft + #### E2E-024L: Resident plugin service is supervised and visible - **Preconditions**: `examples/plugins/hello` enabled with `background.service` granted. diff --git a/docs/spec/08-meta/decisions-log.md b/docs/spec/08-meta/decisions-log.md index 97ea2753fa..7ba936d3bd 100644 --- a/docs/spec/08-meta/decisions-log.md +++ b/docs/spec/08-meta/decisions-log.md @@ -34,6 +34,7 @@ This log freezes previously open questions into concrete decisions. | D637 | Remove the Windows frameless resize rim | **Disable the Windows main window's thick frame while retaining Electron 43.6 native frameless edge and corner resizing. Apply a 4 DIP native rounded shape by default; authorized plugin themes may choose an integer radius from 0 to 24 DIP. Keep the D635 minimum-size contract and existing work-panel resize ownership. See ADR 0317 and E2E-167.** | The thick frame paints an unwanted left, bottom, and right rim that themes cannot remove. Native hit testing and shape keep resizing and transparent outer corners without renderer resize IPC. | | D638 | Publish native Linux arm64 artifacts | **Amend D126 / D285 / D603 / ADR 0022: tag releases publish native Linux arm64 AppImage, deb, and rpm packages, built on GitHub's native `ubuntu-22.04-arm` runner and carrying an arm64 `pi-desktop-host-core`. The static Linux targets drop their pinned `arch` and take the workflow's `--x64` / `--arm64` flag; `linux.artifactName` becomes `PI-Desktop--linux-.AppImage`; each Linux lane verifies its architecture-named updater feed (`latest-linux.yml` on x64, `latest-linux-arm64.yml` on arm64); the ASAR export reads `linux-unpacked` or `linux-arm64-unpacked` and publishes `PI-Desktop--linux-.asar`. `pi-host-bundle` builds both Linux architectures and `PUBLISHED_TARGETS` gains `linux-arm64`. Updater ownership, signing, and delivery modes are unchanged. See ADR 0318, issue #1281, and E2E-192a.** | arm64 Linux devices could not install or run the published x64 artifact, and a cross-built or emulated lane would ship a mismatched Rust sidecar. | | D639 | models.dev owns published chat-model metadata | **Supersede D136 / D266 and ADR `pi-ai-core-0991-authority` for chat metadata: the bundled and explicitly refreshed models.dev catalog supplies published chat-model limits, modalities, reasoning metadata, names, and prices. Prefer the selected official publisher; when it has no record, accept another publisher only for a safe, unambiguous match, otherwise keep generic metadata. The checked-in preset identities are the priority set; do not assert an unsupported fixed count of 39. Live endpoint/OAuth discovery still owns selectable IDs. Pi remains responsible for OAuth, wire identity, transport and typed non-chat operations, but never supplies sibling chat limits, reasoning or prices. Explicit user binding overrides remain authoritative. No credentials are sent to models.dev; no host schema/protocol or persistence change. See ADR `models-dev-catalog-authority` and E2E-162 / E2E-MODEL-catalog-window-correction-reaches-saved-bindings.** | A Pi sibling default assigned a 272,000-token window to GPT models whose selected models.dev records publish 1,050,000 tokens, changing the Settings display and runtime context budget. | +| D640 | User MCP tools keep the normal approval path | **`mcp__` calls are `medium` risk in host-core: under `ask` and `accept-edits` each call shows the approval card ("MCP server tool requires approval"), allow-once and allow-session keep their usual scope (one call / that exact tool name in that session), `auto` runs without a card, and Plan/Goal still deny. Annotations or risk values the MCP server declares about its own tools are ignored and never lower the path. Dispatch, read-only-mode handling and the `mcp_` namespace are unchanged; no host protocol or persistence change. See ADR `mcp-tool-approval-risk` and E2E-MCP-tool-requires-approval.** | MCP tools were auto-allowed as `low` risk, so a configured server could write files, call networks or run commands without any prompt under `ask`. Configuring a server is consent to launch it, not to every action its opaque tools take. | | D450 | Signed macOS GitHub Releases | **Amend D078 / ADR 0022: GitHub tag releases Developer ID-sign, notarize (`notarytool` via electron-builder 26), staple, and Gatekeeper-verify macOS DMG/ZIP before upload, using identity `Developer ID Application: XingYu Liu (DUV63RKYTW)` / team `DUV63RKYTW` from Actions secrets (`CSC_LINK`, `CSC_KEY_PASSWORD`, `APPLE_ID`, `APPLE_APP_SPECIFIC_PASSWORD`, `APPLE_TEAM_ID`). Missing secrets fail the job. Local unsigned packaging without a certificate remains. `workflow_dispatch` may set `sign_macos: false` only for unsigned debug artifacts. Packaged macOS uses in-app `electron-updater` (ZIP + merged `latest-mac.yml`); Linux deb/rpm and Windows portable ZIP stay notify-and-link. No afterPack/afterSign adhoc codesign (ADR 0278).** | Production DMGs must open without a Gatekeeper warning, and signed macOS installs can download and restart into a new tag. See ADR 0289, E2E-196c, E2E-067A. | ## B. Secondary implementation defaults @@ -7395,3 +7396,16 @@ must keep splitting are covered by `markdown-blocks.test.mjs`. knows only Raspberry Pi CPU parts for Linux arm64. Speech-to-text and the rest of the app have no architecture-specific dependency. See ADR 0318, issue #1281, and E2E-192a. + +## 2026-10-03 — User MCP tools keep the normal approval path (D640) + +- D640 moves `mcp_` tools from `low` to `medium` risk in + `PermissionManager`. Under `ask` and `accept-edits` every call shows the + approval card with the reason "MCP server tool requires approval"; `auto` + runs it without a card, and Plan/Goal deny it even with a session grant. +- Allow-session covers only the exact `mcp__` name in that + session, not the server's other tools. Risk annotations the server declares + about its own tools are not trusted and never lower the path. +- Dispatch over `plugins.execute`, read-only-mode handling and the `mcp_` + namespace are unchanged. See ADR `mcp-tool-approval-risk` and + E2E-MCP-tool-requires-approval. diff --git a/docs/zh-CN/spec/03-runtime/03-tools-and-permissions.md b/docs/zh-CN/spec/03-runtime/03-tools-and-permissions.md index 86c7aac572..5fe4bbb151 100644 --- a/docs/zh-CN/spec/03-runtime/03-tools-and-permissions.md +++ b/docs/zh-CN/spec/03-runtime/03-tools-and-permissions.md @@ -367,6 +367,13 @@ tool/protocol 名称,请求中单独携带固定的 shell ID。 | 中等 | 低风险 network/metadata | 政策确认或允许 | | 高 | Write/Edit/Bash | 默认确认 | +用户配置的 MCP 服务器提供的工具(`mcp__`)归类为 `medium`, +与未声明有效风险的插件工具相同。MCP 服务器自行声明的风险级别不被信任, +这与用户已接受的插件 manifest 中的风险不同。在 `ask` 和 `accept-edits` 下, +MCP 工具调用会显示审批卡片,原因为 "MCP server tool requires approval"; +`allow-session` 授权会在该会话内对该工具名不再提示(授权仅保存在内存中)。 +`auto` 自动允许,Plan/Goal 合约模式的硬拒绝仍然生效(D640,ADR `mcp-tool-approval-risk`)。 + ### 决策类型 - `allow-once` diff --git a/docs/zh-CN/spec/05-security/01-security.md b/docs/zh-CN/spec/05-security/01-security.md index 1dc1f27c92..d776ef9a95 100644 --- a/docs/zh-CN/spec/05-security/01-security.md +++ b/docs/zh-CN/spec/05-security/01-security.md @@ -94,6 +94,11 @@ CDP 插件工具在 Plan 中仍被拒绝)。 Bash 在 Plan 中仍然可用: 结构化 title/question 字段。 Renderer 和 sidecar 状态无法写入或 替换一个工件。 +用户配置的 MCP 服务器提供的工具(`mcp__`)默认绝不视为低风险: +host-core 将其归类为 `medium`,并忽略 MCP 服务器为自身声明的任何风险级别。 +`ask` 和 `accept-edits` 需要审批(`allow-session` 授权在该会话剩余时间内覆盖同一 +工具名,仅保存在内存中),`auto` 自动允许,Plan/Goal 仍然拒绝(D640,ADR `mcp-tool-approval-risk`)。 + ## 4.1 技能市场出网 渲染层不拉取技能目录或 SKILL.md。Electron 主进程按公网策略发起 HTTPS 请求(ADR 0243 / D413,由 ADR 0272 / D436 修订):仅 `https`、共享的公网主机语法检查、`redirect: "manual"`,以及按请求实际会走的线路逐跳判定。每一跳之前,客户端都会向承载 `net.fetch` 的会话询问它自己的代理判定(`Session.resolveProxy`):`proxied` 线路上按线路判定,因此容忍解析器自身产物的那一类(`benchmark`,TUN fake-IP);`direct` 或读不出线路时默认保留完整的本地分类,回环、RFC1918、ULA、link-local、mapped IPv6 以及其他所有非公网类别一律拒绝。显式 `allowFakeIp` 选项仅可为透明路由器/TUN 部署额外放行 benchmark 占位地址。安装只通过 `skills.create` 写入 markdown。内联相邻 markdown 后仍受 128 KiB 宿主上限约束。 diff --git a/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md b/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md index f74bdcbfa7..64fe26caa6 100644 --- a/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md +++ b/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md @@ -1526,6 +1526,14 @@ task-candidate E2E 从请求工作树运行,但使用主工作区已经准备 - **关联规格**:`03-runtime/01-ipc-protocol.md` §12a、`07-plugins/04-plugin-security.md` §8.1 - **状态**:客户端及会话隔离已有单元测试;完整桌面流程待验证 +#### E2E-MCP-tool-requires-approval:用户 MCP 工具在 ask 与 accept-edits 下需要审批 + +- **先决条件**:一个绑定项目的 Agent 会话;一个用户配置的 stdio MCP 服务器,其工具列表把某个工具标注为只读/低风险。 +- **步骤**:1) 会话处于 `ask` 时,让代理调用该 MCP 工具。2) 以“允许一次”回应卡片后再次调用,再以“本会话允许”回应并第三次调用。3) 在新会话中切到 `accept-edits` 并重复调用。4) 切到 `auto` 调用。5) 依次切到 Plan、Goal 调用。 +- **预期**:在 `ask` 与 `accept-edits` 下,每次调用都显示审批卡片,原因为 "MCP server tool requires approval",风险为 `medium`,与服务器自行声明的标注无关。“允许一次”只覆盖该次调用;“本会话允许”只在该会话内对同一 `mcp__` 名称不再提示,不覆盖该服务器的其他工具。`auto` 不显示卡片直接执行。Plan 与 Goal 即使存在会话授权也拒绝。 +- **关联规格**:`03-runtime/03-tools-and-permissions.md`、`05-security/01-security.md`、D640、ADR `mcp-tool-approval-risk` +- **状态**:已有单元测试(host-core `permissions.rs` MCP 风险与模式测试);桌面流程待验证 + #### E2E-024L:常驻插件服务受监督且可见 - **先决条件**:在授予 `background.service` 的情况下启用 `examples/plugins/hello`。 diff --git a/docs/zh-CN/spec/08-meta/decisions-log.md b/docs/zh-CN/spec/08-meta/decisions-log.md index cb33374e86..758fdb9f6c 100644 --- a/docs/zh-CN/spec/08-meta/decisions-log.md +++ b/docs/zh-CN/spec/08-meta/decisions-log.md @@ -37,6 +37,7 @@ | D637 | 移除 Windows 无边框窗口的缩放边缘 | **关闭 Windows 主窗口的厚边框,同时保留 Electron 43.6 原生无边框窗口的边缘和角落缩放。默认应用 4 DIP 原生圆角;获得授权的插件主题可选择 0 至 24 DIP 的整数半径。保留 D635 的最小尺寸约定和现有工作面板缩放归属。见 ADR 0317 与 E2E-167。** | 厚边框绘制了主题无法移除的左、下、右边缘。原生命中检测和窗口形状在不新增渲染层缩放 IPC 的情况下保留缩放能力及透明外角。 | | D638 | 发布原生 Linux arm64 工件 | **修订 D126 / D285 / D603 / ADR 0022:标签发布构建并发布原生 Linux arm64 的 AppImage、deb 和 rpm 包,它们在 GitHub 原生 `ubuntu-22.04-arm` 运行器上构建并携带 arm64 `pi-desktop-host-core`。静态 Linux 目标去掉固定的 `arch`,改用工作流的 `--x64` / `--arm64` 参数;`linux.artifactName` 变为 `PI-Desktop--linux-.AppImage`;每条 Linux 通道校验按架构命名的更新源(x64 为 `latest-linux.yml`,arm64 为 `latest-linux-arm64.yml`);ASAR 导出读取 `linux-unpacked` 或 `linux-arm64-unpacked` 并发布 `PI-Desktop--linux-.asar`。`pi-host-bundle` 构建两个 Linux 架构,`PUBLISHED_TARGETS` 增加 `linux-arm64`。更新器归属、签名和交付模式不变。见 ADR 0318、issue #1281 与 E2E-192a。** | arm64 Linux 设备无法安装或运行已发布的 x64 工件,而交叉构建或模拟的通道会随包发布架构不匹配的 Rust sidecar。 | | D639 | models.dev 拥有已发布的聊天模型元数据 | **就聊天元数据而言,取代 D136 / D266 和 ADR `pi-ai-core-0991-authority`:随应用打包并可显式刷新的 models.dev 目录提供已发布的聊天模型上下文 / 输出上限、模态、推理元数据、名称和价格。优先采用所选官方发布方;其没有记录时,只有安全且无歧义的匹配才采用其他发布方,否则保留通用元数据。仓库中的预设身份是优先集合;不要声称存在未经证实的 39 家固定名单。实时端点 / OAuth 发现仍决定可选模型 ID。Pi 仍负责 OAuth、wire 身份、传输和有类型的非聊天操作,但不再为聊天模型提供同档模型的上限、推理能力或价格。明确的用户绑定覆盖仍具权威性。不向 models.dev 发送凭据;不改主机模式 / 协议或持久化。见 ADR `models-dev-catalog-authority` 与 E2E-162 / E2E-MODEL-catalog-window-correction-reaches-saved-bindings。** | Pi 同档模型默认值曾把 GPT 模型的上下文窗口设为 272,000;所选 models.dev 记录实际发布的是 1,050,000,导致设置页显示和运行时上下文预算错误。 | +| D640 | 用户 MCP 工具保持常规审批路径 | **host-core 将 `mcp__` 调用视为 `medium` 风险:在 `ask` 与 `accept-edits` 下每次调用都显示审批卡片("MCP server tool requires approval"),允许一次与本会话允许保持原有范围(单次调用 / 该会话内同一工具名),`auto` 不显示卡片直接执行,Plan/Goal 仍然拒绝。MCP 服务器对自身工具声明的标注或风险值被忽略,绝不降低审批路径。分发、只读模式处理与 `mcp_` 命名空间不变;不改主机协议或持久化。见 ADR `mcp-tool-approval-risk` 与 E2E-MCP-tool-requires-approval。** | MCP 工具此前按 `low` 风险自动放行,已配置的服务器在 `ask` 下可以不经提示写文件、访问网络或执行命令。配置服务器意味着同意启动它,而不是同意其不透明工具的每一个操作。 | | D450 | 签名的 macOS GitHub Release | **修订 D078 / ADR 0022:GitHub tag 发布使用身份 `Developer ID Application: XingYu Liu (DUV63RKYTW)` / 团队 `DUV63RKYTW`,通过 Actions 密钥(`CSC_LINK`、`CSC_KEY_PASSWORD`、`APPLE_ID`、`APPLE_APP_SPECIFIC_PASSWORD`、`APPLE_TEAM_ID`)对 macOS DMG/ZIP 做 Developer ID 签名、`notarytool` 公证、装订和 Gatekeeper 校验;缺少密钥则失败。无证书的本地未签名打包仍可用。`workflow_dispatch` 仅可把 `sign_macos: false` 用于未签名调试产物。打包的 macOS 走应用内 `electron-updater`(ZIP + 合并后的 `latest-mac.yml`);Linux deb/rpm 与 Windows 便携版 ZIP 仍为通知并打开发布页。禁止 afterPack/afterSign adhoc 签名(ADR 0278)。** | 正式 DMG 应无需 Gatekeeper 警告即可打开,已签名 macOS 安装可下载并重启到新 tag。见 ADR 0289、E2E-196c、E2E-067A。 | ## B. 辅助实现默认值 @@ -5201,3 +5202,13 @@ Markdown 源码,不是 `text/html` 负载;对禁用行内 HTML 的外部编 - 已知限制:除 Raspberry Pi 板卡之外的 arm64 Linux 设备仍无法采集麦克风,因为 `@picovoice/pvrecorder-node` 对 Linux arm64 只认识 Raspberry Pi 的 CPU part。 语音转写和应用的其余部分没有架构相关的依赖。见 ADR 0318、issue #1281 与 E2E-192a。 + +## 2026-10-03 —— 用户 MCP 工具保持常规审批路径(D640) + +- D640 在 `PermissionManager` 中把 `mcp_` 工具从 `low` 调整为 `medium` 风险。在 `ask` 与 + `accept-edits` 下每次调用都显示审批卡片,原因为 "MCP server tool requires approval"; + `auto` 不显示卡片直接执行,Plan/Goal 即使存在会话授权也拒绝。 +- 本会话允许只覆盖该会话内同一 `mcp__` 名称,不覆盖该服务器的其他工具。 + 服务器对自身工具声明的风险标注不被信任,绝不降低审批路径。 +- 通过 `plugins.execute` 的分发、只读模式处理与 `mcp_` 命名空间不变。见 ADR + `mcp-tool-approval-risk` 与 E2E-MCP-tool-requires-approval。