# MCP：独立 L1 Worker 与用户权限

状态：2026-09-08 用户明确决定的目标架构；完整实现和上线验收未完成。

本文是 MCP 重设计的唯一权威来源。它替代此前的 SFCRM AppService 内嵌恢复方案，
以及把 Connected App / MCP scope / 资源 profile 当作必要前提的方案。
旧审查记录保留为历史证据，不再作为本次设计或部署依据。

## 确定的边界

MCP 的全部功能属于独立 L1 Worker，代码位于 `workers/mcp/`，拥有自己的
可执行入口、进程、镜像、版本、workload identity 和生命周期。Worker 的业务
依赖只有 Runtime 发布的 CRM L0 API。

```mermaid
flowchart LR
    Client[MCP 客户端：用户身份] --> Worker[L1 MCP Worker]
    Worker --> Runtime[Runtime CRM L0 API]
    Runtime --> Engine[当前用户权限与 CRM 引擎]
    Engine --> DB[(Runtime 数据库)]
```

Worker 承接 MCP 协议、工具发现、参数转换、结果投影、连接、超时、取消和排空。
Runtime 承担身份校验、用户权限、元数据、查询、CRUD、事务和幂等语义。
L2 承担 PingCAP 环境参数、制品选择、入口及部署验收。

Worker 不读取数据库或 Catalog 存储，不依赖 SFCRM AppService 或 connector
服务，不导入它们的实现，也不复制 CRM 业务逻辑。Worker 名称不要求引入额外队列。

## 权限：用户有什么权限，MCP 就是什么权限

- MCP **不依赖 Connected App（Connector App）**，不要求管理员为 MCP
  注册 Connected App，不建立 MCP 专属 scope、授权 profile 或业务权限表。
- 使用 Runtime 已有的用户登录/SSO、session/token 能力识别用户；每次业务
  调用仍由 Runtime 校验身份有效性和当前权限。
- 用户能读哪些对象、字段、记录，MCP 就能读哪些；用户能创建、修改、删除
  什么，MCP 就能执行相应已实现的工具。没有权限时返回 Runtime 的拒绝。
- 不因为经由 MCP 就把有写权限的用户降成只读，也不以服务账号或管理员
  权限替代调用者。不能用 `ModifyAllData` 之类的特权绕过用户真实授权。
- Worker workload identity 用于服务间认证和已发布 API 的调用登记，不能
  充当用户身份，也不是另一层 MCP 业务权限。实际用户授权始终由 Runtime 判定。
- 用户权限变化、停用、session/token 撤销应由 Runtime 在后续调用中生效。
  Worker 不保存另一份用户权限真相，也不解码 JWT 里的角色来决定授权。

身份与权限仍然需要校验；“不依赖 Connected App”不能实现为匿名访问或跳过
Runtime 校验。凭证的既有有效性规则由 Runtime 维护，Worker 不创造旁路。

## 先复用 Runtime API

源码盘点基线：App-packs `fbb0db2`；Core
`4324361c2`。这是源码覆盖，不是线上通过证据。

| MCP 能力 | Runtime 接口或 owner | 当前结论 |
| --- | --- | --- |
| 用户身份/委托 | `IdentityContext.Exchange`、现有用户 session/token owner | 沿用普通 Exchange；原生 federated session 无 Connected App 源码路径已核对，真实客户端待验 |
| 查询记录 | `RecordQueryService.QueryRecords` | 已有结构化查询 API |
| 查询我的记录 | 同一查询 API 的可信当前用户 operand | 已有机制；用户 ID 不来自客户端参数 |
| 单条记录 | `RecordsService.ReadSelectedRoot` | 已有 API；投影需验证 |
| 创建/更新/删除 | `RecordMutationsService` 和对应粗粒度用例 | 工具属于同一 Worker；权限、事务和幂等由 Runtime 判定 |
| 对象列表与描述 | 当前、按用户权限过滤的元数据 owner | 普通用户 API 覆盖待补证；不能借用 admin API 提高用户权限 |
| 其他 CRM 业务动作 | 对应已发布 Runtime 用例 API | 逐工具映射；缺失能力显式记录，不能直接读库或模拟事务 |

工具目录由 L1 定义，调用时转成已发布 Runtime 请求。MCP 方法名、客户端品牌和
外部字符串查询语法不进入 L0。若缺少必要用例，只向原 Runtime owner 补最小
通用 API；此前 `ExchangeResource` / profile registry 草稿不是既定前置条件。

独立 Worker 的用户凭证与 Runtime endpoint、audience、workload、安装上下文
必须实测匹配。客户端如何取得/携带既有用户凭证也须验证，不默认新增 OAuth
Connected App 流程，不虚构 Claude/Codex 的免配置连接能力。

## 工具与迁移

读、写、描述和业务动作都在同一个 L1 Worker 设计。每个工具声明对应 Runtime
API、输入/输出、错误映射及幂等要求；不得把多个写 RPC 宣称为一个原子事务。
工具可被列出不代表用户获得执行权限，最终判定始终在 Runtime。

旧 URL、工具名、filter 语法、返回格式和客户端连接方式列入迁移清单，逐项
明确保留或显式迁移。不能静默丢弃 filter、扩大查询范围，或把旧组件的测试
结果算作新 Worker 验收。元数据必须来自当前域 owner，不复制可编辑 Catalog
投影，不用 API 名伪造标签，也不靠重新发布 Catalog 才反映普通在线编辑。

## 独立发布

Worker 独立构建和发布，声明实际消费的 Runtime API 能力与协议要求。
workload 和已有 CRM 安装上下文的绑定须遵循当前 L1 注册合同，不凭进程名
或环境变量自行赋权。L2 将 MCP 入口指向 Worker，记录精确 Worker/Runtime
制品身份。正式环境的发布仍遵循已有受控流程。

## 执行与验收

1. 撤下内嵌 AppService 及 Connected App/profile 前提的草稿，建立独立模块。
2. 完成无 Connected App 的用户身份路径与 Runtime API 覆盖矩阵。
3. 实现真实 Runtime 客户端、工具适配、Worker 入口和生命周期；有证据的 API
   缺口由相应 L0 owner 补齐，不在 Worker 复制实现。
4. 验证普通读用户、写用户、字段/记录受限用户、权限变更和撤销；结果必须与
   同一用户直接调用 Runtime 的行为一致。
5. 验证客户端初始化、工具发现、真实读写、取消/超时和排空；发布 Worker，
   完成 L2 入口迁移和端到端验收。

当前已实现独立模块/入口、发布版 Runtime 客户端、查询/我的记录/单条增删改/写入状态工具、进程探针、超时与排空、候选镜像构建和 CI。没有新增 L0 RPC
或 Connected App 配置。独立身份的真实 Runtime 查询、增删改、幂等、记录隔离、
只读拒绝、账号停用与重启恢复已完成 host-only 验收；精确候选及验证边界见
[Core #4837](https://github.com/pingcap-inc/NexusCRM/pull/4837)。普通用户元数据 API、
客户端登录、签名发布与公开入口迁移仍未完成，不能据此宣称正式环境 MCP 已恢复。

### 当前 API 路径和源码证据

- Runtime `backend/internal/runtimeidentity/session_lifecycle.go` 的
  `createFederatedSession` 由既有 federated verifier 与用户映射建立原生 session，
  未设置 `ConnectedAppId`。`nexuscrm-persistence/session_authorizing.go` 对空
  ConnectedAppID 明确不要求应用授权；已关联应用的凭证仍按现有规则验证。
- 普通 `IdentityContext.Exchange` 与每个数据 RPC 保持 workload、installation、
  user session、能力声明和当前权限的现有验证。现有 session scope 是 Runtime
  凭证事实；不新增 MCP scope，也不把无 scope 的凭证解释为任意权限。
- `runtimegrpc.RuntimeClientGroup` 仅按同一 Runtime/workload 组合协调短期委托；
  数据 RPC 仍验证当前 session/permission state。初始化通过不证明某个对象可读，
  工具列出不证明可写。用户态权限没有在 Worker 中缓存或重新计算。
- 新工具输入、结果、客户端限制与命令见
  [Worker README](../../workers/mcp/README.md)。当前为原生用户 bearer 的显式配置
  接入；没有实现或宣称标准 OAuth 自动登录、旧 OAuth/PAT 迁移通过。
- 新工具使用独立的 `crm_*` 名称，明确改变旧 filter/输出合同。所有过滤条件均
  保留或拒绝；`list_objects`/`describe_object` 暂不广告，缺口必须在域 owner 补齐，
  不能以空列表或 admin API 假装普通用户发现成功。

协议参考：[MCP Streamable HTTP](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports)。
当前传输不产生服务器请求，不提供 SSE 事件流或持久 MCP session；GET 返回 405。
通知不执行工具；断开/超时不是写入回滚证据，必须通过原命令状态对账。

### Runtime capability admission

Runtime serving v4 的 `user_api_callers` 显式登记独立用户 API 调用者；该身份也必须
出现在 `authorized_workload_ids` 并配置独立服务凭证，不能兼任 handler owner、
workload consumer 或 Catalog consumer。登记只授权进程访问已安装的查询/写入 API，
不授予用户业务权限，不新增 Connected App，不替换 AppService 身份或重发 Catalog。
Runtime 在委托签发和每次调用中使用同一 prepared Catalog 安装/epoch 与实时安全状态。
该通用 L0 合同由 [Core #4837](https://github.com/pingcap-inc/NexusCRM/pull/4837) 维护。

协议初始化与查询请求选择 `runtime.records.query.v1`；单条写入与状态对账选择
`runtime.records.mutate.v1`。在工具调用前为对应能力取得委托，不把查询委托
复用为写权限。这些是已有 Runtime API 的调用合同，不是 MCP 新增的业务 scope。
Runtime 仍判定该用户能否修改目标对象、字段、记录；同一调用者被拒绝时不执行写入。

单条 upsert 尚未纳入当前 Worker：Core 审计基线的
`nexuscrm-records/runtime_record_mutation.go:executeOperation` 只执行 CREATE、UPDATE、
DELETE。公开类型中的 UPSERT 分支不等于已有可用 Runtime 实现；当前工具不广告或
接收它，也不通过查询后写入模拟 upsert。未来须先绑定一个真实实现的 Runtime 用例。
