Files
qinglong/docs/adr/ADR-0353-bounded-local-mcp-approval-observation.md
T

5.0 KiB
Raw Blame History

ADR-0353:有界、低敏的本机 MCP Approval 观察

  • 状态:Accepted
  • 日期:2026-08-10
  • 关联 RFCQL-RFC-0001 D-05、D-06、D-08、D-13、D-17、D-28、D-75、D-85、D-87、D-157、D-257、D-259、D-260、D-261、D-262、D-263、D-264、D-265
  • 关联 ADRADR-0138、ADR-0346、ADR-0347、ADR-0348、ADR-0349、ADR-0350、ADR-0351、ADR-0352

背景

Local MCP 已能发现 Task、Trigger 与 Run,但调用者看不到一个高风险动作为什么仍在等待、已经拒绝,还是已经消费。 这会诱使 Client 重试写操作或要求返回 Approval 内部记录。现有 Approved Action contract 已有完整状态机,SQLite 与 PostgreSQL 也已有 (project_id, updated_at_ms, request_id) 索引;缺口是只读、低敏、Profile-neutral 的发现端口和 MCP-only projection,不是新的审批状态机、表或 workspace package。

决策

  1. Runtime Core 增加只读 ApprovalRequestSource,固定使用 (updatedAtMs DESC, requestId DESC) keyset,默认由产品选择页大小,contract 最大页为 64。
  2. SQLite 与 PostgreSQL 在既有 storage package 的 approved-action/ domain 内实现该端口,读取 limit + 1 行, 复验 canonical Approval record、record digest 与索引时间镜像。复用现有表和索引,不增加 migration。
  3. Local MCP 增加第六个只读 Tool qinglong.approval.list@1.0.0。输入只允许 afterlimit,Project 只能来自 私有进程配置。每次调用仍经过 authentication、exact Tool permission、approval.read、durable Audit、credential confirm,之后才可读 Project-scoped page。
  4. 输出只包含 request ID、version/state/risk/decision mode、permission、action type、requester type、请求/过期/决定/ 消费/更新时间。不得返回 Project、action reference、任一 digest、requester/decider ID、authentication ID、reason、 fence、dispatch/consumption/decision ID 或 preview 内容。
  5. approval.read 是所有既有 Project role 的只读权限,但不授予 approval.decide、consume、dispatch 或 execute。 Agent 的通用 tool.call:* 审批规则不因此绕过。
  6. cross-Project、乱序、超量、损坏 record/digest、非法 cursor 与 continuation 漂移全部失败关闭;存储错误只映射为稳定 unavailable,不返回数据库细节。
  7. 不增加 package、production dependency、listener、连接、timer、watcher、缓存或写 authority。MCP projection 留在既有 local-mcp-server/tool-projection;默认 Edge/Standalone application 继续不安装 MCP SDK。

Package 粒度裁决

本能力故意跨三个既有 adapter package 纵向实现,而没有建立 approval-discovery workspace package。公共 contract 属于 Runtime Core;方言查询分别属于 SQLite/PostgreSQL;单一 MCP 消费者的字段裁剪属于 MCP package。当前机器账本为 17 个包、 998 个 source,其中 972 个在 domain 子目录、26 个根文件均为 public facade 或 binary/process composition;没有单源或 shallow package。唯一两文件的 local-command-file 仍被四个不同生命周期 production closure 复用且没有 production dependency,合并只会复制安全读取协议或扩大上层权限闭包。

被否决方案

  1. 直接返回完整 Approval record:会泄露 action reference、digest、主体、fence 与执行关联。
  2. 只返回 pending:当前没有 Project+state+order 复合索引;投影层过滤会造成无界补读和游标歧义。
  3. 从 MCP approve/consume/execute:观察能力不具备强人类决策、原子 start barrier、幂等恢复和写审计产品门。
  4. 为 contract、SQLite reader、PostgreSQL reader 或 projection 分别建包:没有独立制品、依赖、权限或版本生命周期。
  5. 把全部本机实现合入单包:会让路由设备的默认 Profile 获得 MCP SDK、Owner authentication 或管理 authority。

验证

  • Runtime Core 450/450、Local SQLite 207/207、Local MCP 33/33、PostgreSQL 289 pass/1 条件 skip。
  • Project Policy、package boundary 与 dependency 专项 61/6117 package/998 source、972 nested、26 root,零 finding。
  • 十二档 artifact 全部 compatible。Standalone Application AI 为 6,253,063 bytes/637 files,距 6 MiB 上限 38,393 bytes Standalone MCP 为 9,839,075 bytes/946 files/201 modulesRSS 增量 37,765,120 bytes。
  • PostgreSQL 18.4 arm64 HA Docker gate gates.passed=truepromotion、remote_apply、旧主 fence/rewind、双 control replica 与领域 COMMIT-response-loss 均通过,结束后临时容器、卷、网络为零。

后续约束

Approval detail、preview Artifact 读取、人工 decide 和任何执行能力必须分别定义权限、低敏字段、MFA/双人复核、事务与恢复 协议,不能扩大本列表偷渡。没有 Project+state keyset 索引前不得增加 state filter。Cluster 若提供 MCP,必须复用同一 Profile-neutral source 与独立进程/连接预算,不能让本机 sidecar 直接连集群数据库。