# FluxDesk Agent 接入指引(MCP 技术协议)

Status: **Current protocol guide with Legacy tool vocabulary**
Product authority: [`产品设计宪章`](./current/00_charter/00_Product_Design_Charter.md)
Program authority: [`程序设计宪章`](./current/00_charter/01_Program_Design_Charter.md)

运行中的 `task`、`requirement`、`inbox`、`需要我决策` 等 tool/route 文本属于兼容协议，不定义新的产品本体或界面语言。高风险 Project Agent 权限走 proposal +「需要我确认」；这不等于所有 human/admin MCP 写入都必须二次确认。

| | |
|---|---|
| 版本 | `v1.39` |
| 最后更新 | 2026-10-25 |
| 适用 FluxDesk 版本 | `v0.26.2532+`(`get_my_work_time` 起) |
| 端点 | `POST /mcp`(Streamable HTTP transport,JSON-RPC 2.0) |
| 鉴权 | `Authorization: Bearer $FLUXDESK_MCP_TOKEN`(用户自助 token) |
| 相关 | [对接「外部业务系统 → FluxDesk」](./INBOUND_INTEGRATION_SPEC.md) · [出站 webhook](./INTEGRATIONS.md) |

---

## 1. 这是什么

FluxDesk 可以让你把自己的 Agent 接进来,让 Claude Code / Claude Desktop / Cursor / Continue / 任何 MCP-compatible 的客户端以"工具调用"的形式读写 FluxDesk 数据 —— 看 inbox、查任务、提交今日同步、关闭需求、给反馈加评论等等。

对普通用户来说,这叫 **接入我的 Agent**。对开发者来说,底层协议是 **MCP(Model Context Protocol)** over Streamable HTTP。后面的配置示例和 tool 表会保留 MCP 术语,因为客户端配置文件、JSON-RPC 方法和工具名都按这个协议命名。

员工 `/me/agent` 路径的产品边界是 **外部客户端 + 用户绑定 MCP token + FluxDesk draft/review/adopt 面**。FluxDesk 不托管、不运行、不唤醒、也不探测你的外部 agent；`whoami` 只验证 token owner/scope,状态只来自 agent 通过 MCP 自报。

文案边界:产品页面、公告和成员操作文档优先使用「接入我的 Agent」「Agent」「项目助手」;只有在讲客户端配置、tool name、JSON-RPC、scope 或协议细节时才使用「MCP」。

安全边界:

- Token 跟单个用户 1:1 绑定。Agent 只能查看到 / 操作这个用户自己能看到 / 操作的数据。
- 项目隔离在服务端执行。别的项目即使存在,Agent 也读不到。
- 写操作按你的身份进入审计日志。不要把 token 给同事共用。
- 建议先从读取场景开始,再给写权限;写入前让 Agent 先起草 / 汇总,由人确认后再执行。

跟 outbound webhook 的区别:

| | MCP (pull) | Webhook (push) |
|---|---|---|
| **谁发起** | Agent 主动调用 | FluxDesk 主动推 |
| **何时触发** | Agent 决定 | 事件发生时 |
| **适合** | "帮我看看 inbox 有什么"、"创建一个 task" 这种 agent-driven workflow | 接 Slack bridge / Telegram bot / 自建 receiver,实时通知 |
| **典型场景** | 跟 Claude 对话时让它代你操作 FluxDesk | 让外部系统知道你这边的状态变化 |

两者**可以同时用**。

---

## 2. 先连接，再发现权限

首选 OAuth：打开 **我的 → Agent 与授权 → 接入我的 Agent**，选客户端，按该卡片最多三步完成连接。
MCP 主入口是 `https://my.fluxdesk.net/mcp`。实际使用连接卡片或 Project locator 给出的端点；入口域名不决定身份、组织或数据放置。
不要使用旧接入页的 token 签发教程。现有 legacy key 继续按原权限工作，不因文案更新自动升级。

## 2.1 验证与开发者兜底

连接后先运行 `fluxdesk_help`，再运行 `whoami` 和 `ping`，确认身份和权限。配置项存在不等于调用成功。
帮助按这个 key 的实际注册和既有 scope / 人工判断闸门，完整分组列出工具与合成调用示例；对象仍在每次调用时复核权限。
`tools/list` 提供实际输入 schema。示例不是执行证据，也不是可用的业务 ID；写前读取对象，支持时先 dry-run，并使用该工具声明的幂等键。

不支持 OAuth 的客户端：**其他 › 开发者选项 › 使用 Token**。Claude Code 的已交付卡片使用单 Project Token 命令。
先选项目和能做什么，生成命令，复制后私下运行。Token 默认遮罩，只在本人选择显示一次或复制时交付。
凭据仅放在私密 Authorization header / `FLUXDESK_MCP_TOKEN` 等 secret 环境变量中，不进入 URL、Client ID、Client Secret、源码、截图或日志。
不要把 token 拼进 URL；不要把 `sk-flux-...` 粘到 URL / Client ID / Client Secret 里。
已有 legacy key 的 copy/download config、revoke、rotate token 路径仍供本人使用；撤销只标记失效，不硬删除历史。任何新的凭据管理流程必须等用户 UI 确认模型和安全审查完成后才能实现；治理动作由人执行，AI 不能自行签发或扩大权限。
轮询仅用于读取和整理，按"需要我今天处理 / 只是信息 / 建议归档"三类汇总，不自行标记完成，也不把收件箱变成另一张待办表。
在 我的 → Agent 与授权 → 我的 Agent 中查看最近调用和断开。过期或撤销后重新连接；管理员暂停时联系管理员，不换 key 绕过。

## 2.2 交给外部 AI 做：Context Pack handoff

交接从工作项本身发起：在工作项详情页（`/workitems/<task_id>`）把 FluxDesk 上下文交给 Claude Code / Codex CLI 处理，再把结果带回来。旧的 `/me/agent?tab=handoff` 手动挑单面已于 F3（2026-07-04）下线；带 `taskId` 的旧深链会直接落到对应工作项，不带的落在 `/me/agent`。

边界:

- 可见性边界不变：交接与回填只接受当前用户可见项目下的未关闭任务。
- Context Pack 是 Work Contract facade 的最小 handoff 信封，不是新持久表。它只内联任务、Context Brief 和短引用说明；文档正文、反馈正文、commit diff、会议内容、私有资产不进入包体。
- 外部 AI 在员工自己的环境里运行。FluxDesk 不托管、不唤醒、不探测外部 runtime。
- 回填 PR/diff、摘要、验证记录或其他 evidence 时，必须已有可用 MCP token；服务端会拒绝过期任务、已关闭任务，并重新校验 `projectId` 与任务真实项目一致且用户可见。
- 校验通过后创建一条 `agent_question` 审查项，进入「需要我决策」。coordinator 在那里 approve / return / change direction；`/me/agent` 不做第二套审批队列。

## 2.3 员工默认路径 vs managed-agent B 路

当前对员工开放、可对外描述的默认路径只有一条:

- **员工默认路径** = 外部客户端 + user-bound MCP token + FluxDesk draft/review/adopt 面

如果团队后续要把已有的 self-hosted runtime / house agent 接进来,那是 **managed-agent B 路**:

- 先走 `request -> approve -> activate`
- 再登记为 project-scoped external actor
- 仍然只做 draft / review-only participation
- 这是一条治理/registry 路径,不是 FluxDesk 托管 runtime 的承诺

两条路径共同边界:

- FluxDesk 不托管、不运行、不唤醒 agent runtime
- 不主动 probe 外部 agent 是否在线
- 不自动 adopt / auto-approve
- **`/decisions` / 「需要我决策」是唯一人工 review cockpit**
- `/me/agent` 负责接入、token、handoff、status、自报和 draft ref;它不复制第二套审批队列

## 2.4 OAuth 与权限层级（ADR-0047 / ADR-0048）

OAuth 使用授权码 + S256 PKCE，公开 discovery 提供授权端点，动态注册由 `/oauth/register` 完成。
客户端自动注册时不要手填 Client ID / Client Secret。授权由本人在 FluxDesk 选择 Project、期限与层级：

| 层级 | 能做什么 |
|---|---|
| 只能查看 (`view`) | 读取授权范围内的信息 |
| 可以新建 (`record`) | 读取、草稿、沟通；在我的空间或已有直接发布授权时可建待办和安排会议 |
| 可以代我操作 (`manage`) | 在已有直接发布权限范围内继续完成、评论、改期和编辑；不是治理或高影响批准权 |

以上调用 `scopesForAgentTier` 的同一服务端映射；选择层级不扩大本人的实时权限。AI 执行权限必须是 **本次发起人的当前权限 ∩ Project 对该 AI 的上限 ∩ AI 自己的 grant**，不是添加者或运行节点提供者的权限。
MCP 是 system-call interface；新建或修改业务写工具必须同轮带幂等键与不写入的 dry-run。存量工具正逐步对齐，不宣称全部已支持。高影响与治理判断仍由人做；AI 不得自我授权。

当前 OAuth 公共 scope 是 `read` / `write`；token 响应返回内部展开 scopes，challenge 使用 `read:*`。
请求 `read` 尚不约束本人在同意页选择的最终层级，客户端应读取真实 grant，不能把请求当作授权上限。
授权码有效十分钟、只使用一次。连接期限可选 30 天 / 90 天（默认）/ 不过期，鉴权每次检查暂停和过期。
当前 token 响应**没有 `expires_in`，没有 refresh token**；不代表永不过期，也不承诺自动刷新。CIMD、六客户端完整兼容性仍须独立验证。
个人 OAuth 的注册集合仍可能包含重新授权后可用的工具；调用时拒绝缺少的权限，`fluxdesk_help` 只列当前 eligible 的工具。
W1 拥有 `tools/list` 按 key 过滤的后续收敛；本轮帮助不复制另一套权限政策。

## 2.5 托管的项目助手:执行任务的 MCP 工具(HOSTWIRE-CORE-1025)

托管节点(FluxDesk 运行的项目助手)用自己的 Project 凭据(`sk-flux-…`,`agent_connections.kind = hosted`)通过 `/mcp` 执行任务,与 runner 接口同一套规则(ADR-0047 §5、ADR 55)。这些工具**只为托管节点自己的凭据注册**;其他凭据看不到,强行调用回 `hosted_credential_required`。

| Tool | 等同的 runner 接口 | Scope |
|---|---|---|
| `hosted_job_feed` | `GET /api/runner/notifications`(只含本凭据的 Project;`waitSeconds` 0–25 等第一条通知) | `agent:dispatch` + `read:tasks` |
| `hosted_job_get` | `GET /api/runner/jobs/{jobId}` | `agent:dispatch` + `read:tasks` |
| `hosted_job_claim` | `POST …/claim`(之后的写入绑定到本节点的 runner) | `agent:dispatch` |
| `hosted_job_report` | `POST …/{progress,logs,evidence,artifacts}`(`kind`;logs / artifacts 为 advanced 可见) | `agent:dispatch` |
| `hosted_job_events` | `POST` / `GET …/events`(`action` = `post` / `list`;同一 sequence 幂等) | `agent:dispatch` |
| `hosted_job_open_question` | `POST …/open-questions` | `agent:dispatch` |
| `hosted_job_decisions` | `GET …/decisions` | `agent:dispatch` + `read:tasks` |
| `hosted_job_decision_ack` | `POST …/decisions/{decisionId}/ack` | `agent:dispatch` |
| `hosted_job_result` | `POST …/result`(同样的提交检查;通过后 WorkItem 进入待验收) | `agent:dispatch` |
| `hosted_job_artifact_file` | `POST …/artifact-files`(`action` = init / chunk / finalize / abort,同样的大小限制) | `agent:dispatch` |
| `hosted_job_repository_credential` | `POST …/repository-credential`(返回的克隆凭据不要记录或保存) | `agent:dispatch` |
| `hosted_heartbeat` | `POST /api/runner/hosted-agent-instances/{instanceId}/heartbeat`(只能是本凭据自己的实例) | `agent:dispatch` |

- 参数就是对应接口的 body / query 字段(camelCase,接口认的 snake_case 也认)加 `jobId`。`runnerId` / `agentId` 可省略:服务端从凭据和它绑定的实例取得;传了不一样的值回 `runner_identity_mismatch` / `agent_identity_mismatch`(403)。
- 每次调用都按接口的规则判定:任务必须在本凭据的 Project(否则 `job_not_found` 404)、属于本助手(否则 `runner_not_authorized` 403),再由 `can()` 判 `hosted_agent.job.read|write`(`authority_denied` 403)。暂停 / 过期的连接在 MCP 鉴权处被拒(403 / 401)。
- 出错时工具返回 `isError` 与 `{ ok: false, status, error_code, message, remediation? }`,`error_code` 与 runner 接口相同。
- 每次调用计入 `runner_auth_route_usage`(route `mcp:<tool>`,auth_kind `hosted_credential`),不记任何 id 或内容。
- runner 接口 `hosted-agent-instances/{instanceId}/heartbeat` 与 `/presence` 也接受托管节点自己的凭据(只限它自己的实例,否则 `agent_identity_mismatch` 403);runner token 用法不变。
- 节点用 `tools/list` 探测这些工具是否可用。

---


## s102 source additions: content policy and retention preview

Source-only until this round is actually released. Missing organisation content policy means OFF; no provider training-use guarantee or context-layer enablement is implied. These entries supplement the s101 guide.

| Tool | Arguments | Authority and result |
|---|---|---|
| `get_project_ai_content_policy` | `project_id` | `read:projects`; live Project member, bound credentials stay in their Project. Effective allowed/off + reason, organisation/Project layers and versions, provider and can_change. |
| `set_project_ai_content_policy` | `project_id`, `state` (inherit/off), `expected_version`, `operation_id`, `dry_run?` | `write:projects`; current Project administrator using the person's own credential only. AI credentials refused, including dry-run. Project cannot allow more than its organisation. Version conflict requires reread; same operation id replays, writes audited. |
| `get_organization_ai_content_policy` | `organization_id?` | `read:projects`; organisation membership or authorized platform administrator, Project-bound credentials refused. Omitted id addresses deployment policy for org-less Projects; missing policy reads OFF. Returns state/version/provider/model approval and can_change. |
| `set_organization_ai_content_policy` | `organization_id?`, `state` (allowed/off), `expected_version`, `operation_id`, `dry_run?` | `write:projects`; organisation administrator or platform super-admin with the person's own credential; omitted id requires platform super-admin. Project-bound and AI credentials refused, including dry-run. Versioned/idempotent/audited; first creation preserves existing model approval. |
| `preview_project_retention` | `project_id`, `job` (time_vault/meeting_minutes/feedback_anonymous), `cap?` (1–100, default10) | `read:projects`; live Project owner in the same read-only snapshot, no platform bypass. Capped aggregate counts only; no content/row ids/cursors/deletion/audit/run writes. Meetings cap10. Flags, REVIEW and actual execution remain operator controlled. |

This preview grants no AI deletion or flag-enable command. A provider/policy/Project read does not grant a content send. Before any stage-B enablement, preserve explicit OFF state for imported Strict Projects and complete the separate provider/budget/source-retention gates.

## 3. 客户端配置（与已交付连接卡片一致，每客户端最多三步）

以下以主入口 `https://my.fluxdesk.net/mcp` 为例；以连接卡片显示的实际端点替换。客户端版本变化，下面是接入指引，不是各客户端已通过真实旅程的认证。

### Claude

1. 复制连接卡片上的 MCP 地址。
2. 在 Claude 的远程 MCP / 自定义连接设置中添加地址；自动注册时 Client ID / Client Secret 留空。
3. 在 FluxDesk 登录，选权限、Project 和期限并允许。

### ChatGPT

1. 复制连接卡片上的 MCP 地址。
2. 在支持自定义 MCP 的连接设置中添加该地址，使用 OAuth 自动注册。
3. 在 FluxDesk 登录并允许选定范围。

### Codex

1. 运行 `codex mcp add fluxdesk --url https://my.fluxdesk.net/mcp`。
2. 打开客户端的 MCP 登录提示。
3. 在 FluxDesk 允许选定范围。

### Cursor

1. 使用 FluxDesk 卡片中的安装链接（只含地址，不含凭据）。
2. 在 Cursor 确认添加远程 MCP。
3. 在 FluxDesk 登录并允许。

### Gemini

1. 运行 `gemini mcp add --transport http fluxdesk https://my.fluxdesk.net/mcp`。
2. 打开客户端的 MCP 登录提示。
3. 在 FluxDesk 登录并允许。

### OpenClaw / 其他支持 OAuth 的 MCP 客户端

1. 复制连接卡片上的 MCP 地址。
2. 在客户端添加远程 MCP，使用 OAuth 自动注册。
3. 在 FluxDesk 登录并允许。

### Claude Code / 开发者 Token 兜底

1. 在「接入我的 Agent」选 Claude Code；其他客户端用「其他 › 开发者选项 › 使用 Token」。选择 Project 和能做什么。
2. 生成命令；Token 保持遮罩，需要时由本人复制或显示一次。
3. 私下运行复制的命令，再调用 `fluxdesk_help`。不要把真实 Token 发到聊天、日志或仓库。

调试可从私密环境变量注入 header，以下仅列工具，不执行任何业务工具：

```bash
curl -X POST https://my.fluxdesk.net/mcp \
  -H "Authorization: Bearer $FLUXDESK_MCP_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

### 3.7 诊断 smoke-test

如果 `whoami` / `ping` 已经能跑通,但客户端还不确定哪些工具能执行,可以再调用只读诊断工具:

```text
mcp_smoke_test
```

它不会创建 task、feedback、monitoring route 或其他业务数据。返回值会包含:

- `token.scopes` / `token.effectiveScopes`
- `tools.registeredCount`
- `tools.callableCount`
- `tools.blockedByScopeCount`
- 每个工具的 `requiredScopes`、`canCall`、`missingScopes`

使用方式:

```text
Use FluxDesk MCP to run mcp_smoke_test. Summarize which tools are callable and which important tools are blocked by missing scopes. Do not write anything.
```

注意: `mcp_smoke_test` 是诊断能力矩阵,不是连接成功的唯一标准。第一次接入仍然必须先真实调用 `whoami`,再调用 `ping`。如果 `mcp_smoke_test` 显示 `create_task` / `create_feedback` / `add_monitoring_route` 缺少 scope,这是预期的 read-only token 行为,不要为了测试去创建真实数据。

### 3.7 两级验证与排错

每个客户端都必须两级验证:

1. 配置存在:客户端 UI 能看到 `fluxdesk`;Codex CLI 可用 `codex mcp list`。这一步只证明配置存在。
2. 验证实际可调用:实际调用 `whoami`,再调用 `ping`。两者成功后才允许写操作。

常见问题:

- env not inherited:桌面 App 没继承 token。按 OS 的稳定桌面命令设置后完整重启 App。
- app not fully restarted:菜单栏 / 托盘 / 后台进程仍在。彻底退出再打开。
- URL wrong:必须是 `https://my.fluxdesk.net/mcp`。
- token in URL:立即移除、撤销/轮换 token,改用 header/env var。
- list_tools failed:`list`/配置成功不等于工具可调用。先 `whoami` / `ping`。
- HTTP unsupported needs bridge:客户端不支持 remote HTTP MCP 或 header/env auth 时,使用 bridge/local proxy。
- proxy/firewall:确认可以访问 `my.fluxdesk.net` 的 HTTPS 和远程 MCP 流量。

### 3.8 批量请求 (JSON-RPC 2.0 batch)

FluxDesk 完整支持 JSON-RPC 2.0 batch:在一个 HTTP POST 里发数组形式的多个请求,server 端逐个执行,返回数组形式的多个响应。底层走 `WebStandardStreamableHTTPServerTransport` (官方 MCP SDK 的实现),自动识别 `Array.isArray(body)` 并 fan-out。

**用例**:agent 一次握手前的轮询 ─ `tools/list` + `whoami` + `ping` 三连合一,省两个 RTT。

```bash
curl -X POST https://my.fluxdesk.net/mcp \
  -H "Authorization: Bearer $FLUXDESK_MCP_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '[
    {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"ping","arguments":{}}},
    {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"whoami","arguments":{}}}
  ]'
```

返回:

```json
[
  {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"{...}"}]}},
  {"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"{...}"}]}}
]
```

注意:

- **建议批量大小 ≤ 20**:超过的话单个请求可能撞到 8s gateway timeout。SDK 不强制截断 ─ 客户端自己负责拆分。
- **顺序无关**:server 并发执行,响应数组的顺序跟请求顺序一致 (按 `id` 配对就行)。
- **错误是局部的**:某条 sub-request 失败只让那一条返回 error envelope,其它 sub-requests 照常返回 result。
- **`initialize` 例外**:batch 里不能塞多个 initialize,SDK 会 -32600。普通 tools/call 不受影响。

---

## 4. Tools 一览

> Tools 集合会随版本演进。运行 `tools/list` JSON-RPC 拿到当前真实清单。

### 读类(read-only)

| Tool | 输入 | 返回 | 用途 |
|---|---|---|---|
| `whoami` / `ping` | — | `{ userId, email, name, role, now }` | 验证 token 工作 |
| `fluxdesk_help` | — | `{ schemaVersion, you, objects, rules, needMore, nextSteps, docs }` | **从这里开始**(任何 token,无需 scope):对象模型、规则、代表谁、能到哪些 Project、本凭据现在能调用的全部工具(与调用时的 scope 判断一致)、怎样拿到更多权限(v1.37,ADR-0047) |
| `get_decision_chain` | `decision_id`, `after_seq?`, `limit?`(≤ 100) | `{ decision{status, confidence, expected_outcome, review_at, …}, steps[], next_after_seq }` | 读一条决策的链：只追加的步骤(提出/定题/请人判断/决定/结果/复盘/推翻/重开/修订…)，每步的执行者、时间、有效时间、原因与链 id。仅决策所在 Project 的在职成员可读；不存在与无权限同一 not-found。scope `read:decisions`(2026-10-24, ADR 135) |
| `list_inbox` | `unreadOnly?: boolean`, `type?: "task" \| "review" \| "meeting" \| "system"`, `attention?: "needs_me" \| "mentions"`, `limit?: number` | `{ totalCount, unreadCount, items[] }` | 看通知列表;`attention=needs_me` = 需要我(含等你处理或等你补充的反馈:平台反馈给超管、项目问题反馈给该项目负责人,一人处理后从所有人的需要我移走),`attention=mentions` = 提到我(聊天以外的 @;聊天里的 @ 仍只在沟通)(v0.26.2668) |
| `get_notification` | `notificationId: string` | 完整一条通知 + actor / 目标 URL | 看单条详情 |
| `list_tasks` | `assignee?: "me" \| userId`, `project?: slug`, `status?`, `priority?`, `limit?` | `{ items[] }` | 查任务 |
| `get_task` | `code: "REQ-1234"` 或 `task_id: uuid`(v0.20.29 起 `code` 为 canonical 入参;`task_code` 仍接受为 deprecated alias) | 完整 task + assignees + status history + `task.work_item` 统一属性视图(v0.26.531) | 看单个任务 |
| `get_work_item` | `id: uuid` 或 `code`(REQ-/DISP-/WI- 都可解析;v0.26.531) | `{ work_item }` 统一属性视图:`decision_state`/`execution_state` 状态机对(恰好一个非空)、`lifecycle` 投影、`source` 分诊来源、`parent_id`+`depth`、`executable` | 用统一 WorkItem 词汇读任意工作项 |
| `get_my_work_time` | `period_kind?: "day" \| "week" \| "month" \| "year"`, `anchor_date?: YYYY-MM-DD`, `range_end_date?`, `project_id?`, `work_item_id?` 或 `work_item_code?` | 调用人自己的 canonical 工时区间、项目/来源明细和 `draft` / `returned` / `submitted` / `confirmed` 分状态汇总；按 WorkItem 过滤时同时返回 `effort_estimate_hours` | 读取自己的实际工时与估时对照；不返回别人的私有草稿，Project-scoped token 始终限定在绑定项目 |
| `list_my_delivered_records` | `project_id?`, `limit?`(≤100) | 调用人自己的「已交付」记录：已获验收回执的工作项 + 本人已确认工时（与 工作台 › 我的工作 › 已交付 同一读取） | 只读，scope `read:tasks`；只返回本人仍是成员的 Project；不汇总、不比较；`window_limit` 是跨项目源窗口，`truncated: true` 表示可能还有更早记录；Project-scoped token 始终限定在绑定项目 |
| `list_my_workbench_updates` | `category?: all \| needs_me \| mentions \| work \| code \| external`, `project_id?`, `limit?`(默认 30，≤100) | 调用人自己的工作台「动态」行，`line` 为界面所示「<系统名> · <标题>」；`availability: unavailable` 表示读取失败而非为空 | 只读，scope `read:inbox`；不标已读、不按人计数；Project-scoped token 始终限定在绑定项目 |
| `retire_my_workbench_draft` | `draft_id`, `idempotency_key`=`workbench-draft-retire:<draft_id>`, `dry_run?` | 与 App 续写成功后的同一软删除命令；本人 task/work_intent 草稿；预览 would_delete，重复 already_gone；返回 AI 代本人归属 | `write:tasks:draft`；私有账户数据，Project 绑定凭据不可用；与硬删除 delete_draft 不同 |
| `list_projects` | — | `{ items[] }` | 自己能看到的项目列表 |
| `list_project_repositories` | `project_slug` | `{ project_slug, count, repositories: [{ id, provider, full_name, web_url, ssh_url, default_branch, is_monitored, archived }] }` | 列出项目绑定代码库。仅项目活跃成员可读(平台角色本身不授权,非成员与不存在的项目同样返回 not found);scope `read:projects` |
| `list_project_repository_commits` | `project_slug`, `repo_id?`, `branch?`, `limit?` | `{ project_slug, count, branch_filter, commits: [{ repo_full_name, provider, short_sha, title, url, actor, occurred_at }] }` | 读取项目已同步 commit 摘要。仅项目活跃成员可读(平台角色本身不授权);scope `read:projects` |
| `list_project_connected_systems` | `project_id` | `{ schema, can_connect, systems: [{ system_id, kind, icon, name, purpose, status(ok/waiting/failing/pending_approval/paused), last_received_at, openable }] }` | 一个 Project 已连接的系统(告警/监控/OA 等来源、待批准的连接申请、代码库、项目工具、服务器监控)及状态。仅项目活跃成员可读;只含元数据;scope `read:projects`(v1.34) |
| `get_project_connected_system` | `project_id`, `system_id` | `{ schema, system, attempts }`:系统状态、最近收到时间与最近 20 次推送(新在前)(含被拒原因与 HTTP 状态:401 Token 无效/已轮换/已撤销、422 缺字段名、429、已暂停);不含正文、请求头、IP 或 Token | 系统详情。只有项目管理员能打开来源(普通成员与非成员都回 `not_found`);scope `read:projects`(v1.34) |
| `get_project_modules` | `project_id` | `{ schema, project_id, can_manage, navigation: { version, … }, layout_web_target, modules: [{ module_id, label, default_label, display_name, kind(fixed/locked_on/toggleable), enabled, version, record_count }], features: [{ module_id: "work_time", label, enabled, version, history_web_target }] }` | Project 设置 › 功能与页面:每个模块是否安装、Project 的顺序与显示名(`navigation`);里程碑 · 文档 · 问题反馈 · 沟通 · 会议 可移除/重新安装(数据保留,v0.26.2668)。`features` = 没有导航入口的功能模块(工时 `work_time`):移除后该 Project 新建或修改工时申报回 `module_disabled`,已提交的仍可确认/退回,历史保留(管理员用 `get_project_work_time` 读)。仅项目活跃成员;scope `read:projects`(v1.34) |
| `milestone_list` | `project_id`, `status?`, `cursor?`, `limit?`(≤100) | `{ schema, project_id, project_name, can_edit, milestones: [{ milestone_id, name, description, status, status_label, overdue, starts_on, target_end_on, starts_at, target_end_at, completed_at, version, progress: {done,total}\|null, linked_item_count }], total_count, next_cursor }` | Project 的里程碑(网页「里程碑」页同一事实):按目标日期排序、不含已归档;`progress` = 你可见的未关闭执行工作项 已完成/总数(与网页/App 同一口径);`starts_on`/`target_end_on` 为你所属时区的日期;改动时把 `version` 作为 `expected_version`。只限 Project 活跃成员;里程碑模块已移除时回 `module_disabled`;scope `read:projects`(v1.39) |
| `milestone_get` | `project_id`, `milestone_id`, `items_cursor?`, `items_limit?`(≤100) | `{ schema, project_id, can_edit, milestone: {…, archived_at}, items: [{ work_item_id, code, title, kind, status, status_label, settled, due_at, responsible_name, assignee_name, decision_owner_name }], items_total, next_items_cursor }` | 一个里程碑(已归档也可读)及其关联工作项,顺序同网页(未完成在前、截止日近在前);只列出你可见的工作项(仅参与者可见的工作项不外泄);scope `read:projects`(v1.39) |
| `list_project_audit_log` | `project_id`, `action_type?`(project/membership/credential/work), `actor?`(`actor_options` 的值:system / user:<id> / agent:<id>), `object_type?`(project/credential/work), `from?` / `to?`(YYYY-MM-DD,按返回的 `timezone`,含两端), `offset?`(上一页的 `next_offset`) | `{ schema, project_id, timezone, filters, rows: [{ id, action, action_label, actor_name(null=系统), actor_kind(user/ai_agent), target_label, created_at }], total, offset, next_offset, actor_options: [{ value, label }] }` | Project 设置 › 数据与治理 › 操作记录:与 Web 数据页、App 同一份记录与筛选,新在前,每页 50 条(offset 上限 950,再往前按日期缩小)。项目活跃成员可读,平台 operator 与非成员回 `not_found`;只读;本人个人 Token;scope `read:audit`(v0.26.2669) |
| `check_project_module_operation` | `project_id`, `module_id?`(reorder / rename 时不带;工时为 `work_time`), `operation_id` | `{ schema, status(applied/not_applied), operation_id, module }`(`work_time` 时 `schema` = `project-feature-modules/1`) | `set_project_module_enabled` 结果不明时核对,只读,从不重试写入;scope `read:projects`(v1.34) |
| `project_rules_list` | `project_id` | `{ schema, project_id, can_manage, can_read_runs, read_only_reason, rules: [{ rule_id, name, sentence, trigger, action, status(active/paused), paused_reason, initiator, version, last_run }], sources, members, limits }` | Project 设置 › 自动化(rules-1026,D1):一条规则 = 一个触发(`system_message` 收到已连接系统的消息,`source_id` null = 任一系统 · `workitem_status` 工作项变为 `to_status`)+ 一个动作(`notify_members` 通知 `user_ids` · `workitem_draft` 给 `owner_user_id` 建工作项草稿,只进其草稿箱,不发布)。`initiator` = 发起人(规则以其名义运行)。仅活跃成员;管理员/协调员另得 `sources` / `members` 选项。AI 可读;scope `read:projects`；归档后只读 (`project_closed`)，按原权限读取运行历史和操作回执；`can_read_runs` 与 `can_manage` 分开，`read_only_reason` 表明归档；已应用操作仍返回 `already_applied`，不重新写入。 |
| `project_rules_get` | `project_id`, `rule_id` | `{ schema, rule }` | 读一条规则(同上字段);scope `read:projects` |
| `project_rules_runs` | `project_id`, `rule_id?`, `limit?`(≤50) | `{ schema, project_id, runs: [{ run_id, rule_id, rule_name, ran_at, trigger, action, outcome(done/partly_done/skipped/failed), detail }] }` | 运行记录,新到旧;只记规则、事件与结果,不记由谁引起,不按人汇总;工作项标题仅对可见者显示。只限管理员/协调员;scope `read:projects` |
| `project_rules_check_operation` | `project_id`, `operation_id` | `{ schema, status(applied/not_applied), operation_id, kind, rule_id }` | 规则修改结果不明时核对,只读;查到未应用后只能用原 operation_id 和原命令重放,不能新建另一操作;scope `read:projects` |
| `project_rules_dry_run` | `project_id`, `rule_id` 或 `rule{ name, trigger, action }` | `{ schema, dry_run: true, days(7), sentence, scanned, would_run, matches: [{ occurred_at, trigger, action, detail }], truncated, would_pause }` | 试运行:最近 7 天会触发几次、会做什么、是否会因过频暂停;不发通知、不建草稿、不写任何记录。治理动作:只限管理员/协调员本人 Token,任何 AI 凭证(Agent、助手、OAuth 连接、Agent 绑定或单 Project Token)回 `human_only`;scope `read:projects` |
| `project_rules_create` | `project_id`, `name`(≤60), `trigger`, `action`, `operation_id`(UUID), `dry_run?` | `{ schema, outcome, operation_id, rule }`;`dry_run: true` 回 `project_rules_dry_run` 结果且不写 | 新建自动化规则,创建者成为发起人;收件人/草稿接收人须为活跃成员,`source_id` 须为本项目已连接系统;每项目最多 20 条;每条每小时最多运行 20 次,超出或规则链深度 ≥ 8 自动暂停并给管理员一条「需要我」;发起人离开或不再是管理员/协调员时暂停;高影响动作永不作为规则动作。只限管理员/协调员本人 Token(AI 回 `human_only`);同 `operation_id` 重放 `already_applied`;scope `write:projects` |
| `project_rules_update` | `project_id`, `rule_id`, `name`, `trigger`, `action`, `expected_version`, `operation_id`, `dry_run?` | `{ schema, outcome, operation_id, rule }` | 改规则(改后以修改者为发起人);`version_conflict` = 期间被改,重读;本人 Token;scope `write:projects` |
| `project_rules_pause` | `project_id`, `rule_id`, `paused`, `expected_version`, `operation_id` | `{ schema, outcome, operation_id, rule }` | 暂停 / 恢复(`paused: false`);恢复者成为发起人,暂停期间的事件不补做;本人 Token;scope `write:projects` |
| `project_rules_delete` | `project_id`, `rule_id`, `expected_version`, `operation_id` | `{ schema, outcome, operation_id, rule: null }` | 删除规则及其运行记录(已发出的通知、已建的草稿留给接收人);本人 Token;scope `write:projects` |
| `list_project_templates` | — | `{ schema, builtins[], saved[], organisations }`;每张卡 `{ ref(`builtin:<id>:<version>` / `saved:<uuid>:<version>`), name, purpose, modules:[{ module_id, label }], human_members, starter_items, visibility, can_manage }` | 新建项目第一步 / 更换模板 的模板卡片(9 个内置模板 + 自己保存或分享给组织的模板);来源 Project 的成员身份不会让保存的模板可见;只读;scope `read:projects`(v0.26.2668) |
| `get_project_template` | `project_id`, `template?` | `{ schema, project_id, state: { ref, name, version, home }, preview? }`(带 `template` 时 `preview` 同 preview_project_template_switch) | 一个 Project 当前记录的模板(`state.version` 0 = 没有记录:我的空间读作 个人空间,其他读作 多人协作);仅项目活跃成员;scope `read:projects`(v0.26.2668) |
| `preview_project_template_switch` | `project_id`, `template` | `{ schema, target, install[], remove[], rename[], order_changed, order_after[], human_members: { from, to }, data_kept: true, current.version, composition, can_manage, refusal }` | 更换模板的预览,从不写入,Agent Token 也可用;`refusal`:`home_fixed` / `has_human_members` / `no_change` / `template_unavailable` / `forbidden`(只能查看) / `not_found` / `version_conflict`,null = 可以确认;scope `read:projects`(v0.26.2668) |
| `check_project_template_operation` | `project_id`, `operation_id` | `{ schema, project_id, operation_id, outcome(applied/not_applied), state }` | `switch_project_template` 结果不明时核对:`outcome` = applied 表示已生效,not_applied 表示没有生效、按返回的 `state` 重新决定;只读,从不重试写入;scope `read:projects`(v0.26.2668) |
| `get_project_assistants` | `project_id` | `{ project_id, can_manage, assistant{ assistant_id, name, connection(ready/needs_setup/stale/offline/error/waiting_authorization) }\|null, service{ state(inactive/activating/setup_failed/active/paused/payment_blocked), version, can_pause, can_resume, waiting_for_node, can_cancel_request, can_switch_to_fluxdesk_hosted, switch_needs_node }, hosted_available, member_access_enabled, usage{ status, rows[{ agent_id, name, run_count, tokens_today, tokens_7d, tokens_30d, tokens_total }] }\|null }` | Project 设置 › 项目助手(App 原生同一读模型 `project-assistants/1.0`)。项目 live 成员可读(运营身份除外);用量只给管理者;`waiting_for_node` = 托管请求超时没有节点接手或已失败,`switch_needs_node` = 改用 FluxDesk 托管适用但暂时没有可用节点(界面显示为不可点并说明原因);scope `read:projects`(APPNAT-S1-1024;HOSTCAP-1025 / s100) |
| `set_project_assistant_service` | `project_id`, `assistant_id`, `desired_state`(active/paused/cancel_request/switch_to_fluxdesk_hosted), `expected_version`, `confirmed:true`(取消时) | `{ outcome(changed/already_current), view }` | 暂停/恢复项目助手;对等待节点或已失败的托管请求:`cancel_request` 取消(助手离开项目)、`switch_to_fluxdesk_hosted` 改用 FluxDesk 托管(没有可用节点回 `hosted_unavailable`);与网页表单同一命令;只限 Project owner/lead 本人 Token(Agent Token → `agent_token_refused`);版本冲突 `version_conflict`;scope `write:projects`(APPNAT-S1-1024) |
| `list_restorable_ai_assistants` | `project_id` | `{ rows: [{kind,targetId,name,removedAt,expiresAt}] }` | 最近取消、七天内可恢复;活跃 Project 成员,助手按管理/添加权限,连接只限本人;scope `read:projects`;纯人类个人 Token |
| `restore_ai_assistant` | `project_id`, `kind`(assistant/agent_connection/ai_connection), `target_id`, `removed_at` | `{ outcome(restored/already_restored), waitingForNode? }` | 恢复原身份/记忆/项目授权,凭据重新签发;旧密钥继续失效;托管等待节点且保留容量闸门;MCP 不签发或返回长期密钥,外部/非托管恢复返回 `primary_sign_in_required`,须本人网页登录;scope `write:projects`,恢复连接还需 `write:profile`;过期/版本/替代助手冲突明确拒绝 |
| `remove_ai_assistant` | `project_id`, `assistant_id`, `confirmed:true` | `{ removed:true }` | 先确认再软移除,撤销访问,保留身份与历史,七天内可恢复;scope `write:projects`;不影响人的设备会话 |
| `set_project_assistant_member_access` | `project_id`, `enabled` | `{ outcome(changed/already_current), view }` | 是否允许成员使用项目助手;只限 Project owner/lead 本人 Token;scope `write:projects`(APPNAT-S1-1024) |
| `get_project_work_review` | `project_id`, `week?` | `{ ok, review }`(`mobile-daily-sync-review/1.0`:week、rows、historicalTraces、counts、page) | 每日同步 › 工作回顾,与 App 原生同一查询;项目 live 成员、同一工作项可见性、只看本人;Project-scoped token 可用;scope `read:tasks`(APPNAT-S4-1024) |
| `get_focus_progress` | `project_id`, `work_item_id` | `{ ok, progress{ contract:"mobile-focus-progress/1.0", projectId, workItemId, title, progressPercent, progressNote, expectedWorkItemVersion, canWrite } }` | 重点进展读取,与 App 原生同一投影;项目 live 成员 + 工作项可见;注册 Agent 读取不获得人类执行人写权限(`canWrite` false);Project-scoped token 可用;scope `read:tasks`(APPNAT-S4-1024) |
| `update_focus_progress` | `project_id`, `work_item_id`, `expected_work_item_version`, `command_id`, `idempotency_key`, `percent`(0–100,5 的倍数), `note?`(≤200) | `{ ok:true, receipt }`(`mobile-focus-progress-receipt/1.0`:replayed、finalStatus、appliedWorkItemVersion、executionState、progressPercent、progressNote、changed)或 `{ ok:false, error }` | 只限人类执行人;100% 仍需交付依据和确认人;重放会重新检查当前权限;Project-scoped token 可用;scope `write:tasks`(APPNAT-S4-1024) |
| `get_my_work_summary` | `start`, `end`(本地 YYYY-MM-DD,含两端,≤31 天) | `{ ok, contract, summary{ contract, startDay, endDay, configured, snapshot{ id, summaryText, summaryError, generatedAt }\|null } }` | 我的工作汇总(每日同步),只看本人;只限本人个人 Token,Project / 注册 Agent Token 拒绝;scope `read:daily`(APPNAT-S4-1024) |
| `generate_my_work_summary` | 同 `get_my_work_summary` | 同上 | 显式同步生成汇总;传输中断时可能已保存,重新生成前先读;无后台任务、无幂等承诺;只限本人个人 Token;scope `write:daily`(APPNAT-S4-1024) |
| `get_project_decision_review` | `project_id`, `decision_id` | 与 App 决策详情同一已授权投影(完整渲染正文、可执行信号提议的执行人/交接字段、仅作背景的选项) | 只读,不批准不采纳;Project token 只能读本项目(`assertProjectScope`),行的 `source.projectId` 不符即拒绝;scope `read:decisions`(APPNAT-S5-1024) |
| `dispose_feedback` | `feedback_id`, 处理方式与其字段(`create_work_item` / `link_work_item` 等), `signal_kind?`(feedback/idea/bug/risk/external_request) | `{ receipt_id, feedback_id, project_id, work_item, replay }` | 反馈「更多处理」,与网页/App 同一处理服务和权限。反馈还没有确认类型时可带 `signal_kind`,在同一事务里记下(审计 `feedback.signal_kind_confirmed`),已有类型不改;缺类型时回 `validation_failed`(title `kind_required`,field `signal_kind`);Agent 调用带 `signal_kind` → `validation_failed`(APPNAT-S6-1024);人调用需要 scope `write:tasks:publish`;别人提交的平台反馈(无 Project)还需要 `triage:feedback` 和平台反馈处理权限,提交者本人可以把自己的反馈收进自己的 Project;Project 助手用 `propose:workitems:publish` 只生成提议。看不到的反馈与不存在的一样回 `not_found`(SEC-1027) |
| `set_feedback_status` | `feedback_id`, `status`, `note?`, `client_message_id`, `expected_version?` / `expected_status?`, `dry_run?` | `{ ok, feedback_id, status, current_status, source_version, dry_run, replayed, status_steps, url }` | 开始处理即已采纳：先 `accepted`，再 `in_progress`，修复上线后用 `add_feedback_comment(mark_shipped=true)` 回复。沿用 Web/App 的 `feedback.triage` 权限，Project 行还需当前在籍；scope `write:feedback`。状态目标必须属于当前 `feedbackHandlingStatusTargets`；`rejected` / `duplicate` 继续用现有关闭命令。未决定状态直接推进时同一事务先记采纳。写入必须提供 `expected_version` 或 `expected_status`；不匹配回 `stale`。`client_message_id` 按凭据去重，相同请求重放，换意图回 `request_conflict`。`dry_run=true` 返回当前版本及有序步骤，不写入、不占用去重键。AI 状态及可选备注按代理身份审计，保留代办人的归属。 |
| `close_feedback` | `feedback_id`, `outcome`(`declined` / `duplicate` / `resolved_elsewhere`), `duplicate_of_id?`(仅 `duplicate`,必填), `reply`(必填), `client_message_id`(必填), `source_version?` | `{ ok, feedback_id, outcome, status(rejected/duplicate), duplicate_of_id, project_id, replayed, url }` | 不转成工作项就关闭一条反馈并回复提交者,与网页/App「更多处理」同一 canonical 命令(`feedback-handling/1`,不予采纳 / 标记重复):`declined` 与 `resolved_elsewhere` 置 `rejected`(审计 `closeOutcome` 区分),`duplicate` 记下原反馈(同一 Project 或平台反馈池)并置 `duplicate`。回复(加 `MCP:` 前缀)、状态与审计在同一事务里落地。权限与网页相同:平台反馈只有平台超级管理员;Project 反馈需要该 Project 的在籍成员且有反馈处理权限(该 Project 的 owner/lead 也可不予采纳等他处理的反馈,但不能标记重复);其他 Project 的管理员、普通成员、提交者本人都被拒绝且不改任何数据。已关联工作项的反馈回 `status_locked`;已关闭回 `invalid_input`。同一 `client_message_id` 同一关闭重试回 `replayed: true`,换回复或换 `outcome` 回 `request_conflict`;带 `source_version` 时反馈已变化回 `stale`。`ai_agent` 身份与接入的 Agent 都拒绝(需要本人决定);Project 绑定的 Agent 令牌看不到此工具。scope `triage:feedback`(MCPFBCLOSE-1025;SEC-1027 起不再由 `write:feedback` 满足) |
| `list_platform_connections` | `query?`, `group?(all/project/personal/system)`, `status?`(含 `problem`), `page?` | `{ schema, page, page_count, page_size(50), total, truncated, counts, connections: [{ connection_id, group, kind, name, project, owner, status{ kind, … }, last_received_at, use_count, can_revoke_here, can_open_project }] }` | 系统设置 › 平台连接:全公司的连接(项目连接、个人 Token、系统代领),只含元数据(无消息内容、规则、Token 前缀或哈希);`truncated` = 某张表超过 500 行,请用 `query` 缩小;仅平台管理员;scope `admin:tokens`(v0.26.2668) |
| `list_hosted_ai` | `state?`(all/problem/waiting/paused/ok/stopped), `project_id?`(`none` = 没有项目), `node_id?` | `{ schema:"hosted-ai-admin/1.0", hosted_ai: [{ id, name, acts_for, project{ id, name }\|null, runtime_node{ id, node_id }\|null, state{ kind, problem }, last_heartbeat_at, version(null = 未上报), ghost, created_at }], counts, total, truncated, ghost_count }` | 管理 › FluxDesk 托管:平台上每个由 FluxDesk 托管的项目助手,「<AI> 代 <添加人>」、项目、运行节点、状态(正常 / 等待第一次心跳 / 有问题 · 原因 / 已暂停 / 已停用)、最近心跳、版本;有问题的在前;`stopped` 只在 `state: "stopped"` 时列出;`ghost_count` = 没有项目、从没连上过的空记录;`project_id` / `node_id` 不是 id(或 `none`)时回 `validation_failed`,从不放宽成全部。只含元数据;仅平台运行运维(super_admin / admin / loop_operator)本人 Token,Agent / 单 Project / 接入的 Agent Token 回 `agent_token_refused`;scope `admin:monitoring`(v0.26.2676) |
| `get_hosted_ai` | `id` | `{ schema, hosted_ai, connection{ id, access(read/write), state, since, last_used_at }\|null, jobs: [{ id, state, created_at, started_at, finished_at }], heartbeats: [iso], changes: [{ type, from, to, at }] }` | 一个 FluxDesk 托管助手:身份与权限(来自它的托管连接,不超过添加人的权限)、最近任务只给 id / 状态 / 时间(从不返回任务内容)、心跳与状态变化;同上权限;scope `admin:monitoring`(v0.26.2676) |
| `list_runtime_nodes` | — | `{ schema, runtime_nodes: [{ id, node_id, hostname, region, state{ kind, problem }, last_heartbeat_at, version, hosted_count, capacity\|null, usage{ cpu, memory, disk }, started_at }] }` | 管理 › 运行节点:状态(正常 / 等待第一次心跳 / 有问题 · 离线 / 不稳定 / 心跳中断 / 已暂停 = 维护)、最近心跳、版本、上面有几个未停用的项目助手(`hosted_count`)、属于几个项目(`project_count`)、上报时的容量与使用率、最近启动(节点按计划定期重启,刚启动属正常);只读,节点没有暂停/排空命令;scope `admin:monitoring`(v0.26.2676) |
| `get_runtime_node` | `id` | `{ schema, runtime_node, hosted_ai: [...], metadata }` | 一个运行节点、上面的项目助手、节点上报的元数据(写入和读取时都会脱敏);scope `admin:monitoring`(v0.26.2676) |
| `pause_hosted_ai` | `id` | `{ schema, ok:true, instance(changed/unchanged/skipped_not_member), connection(changed/unchanged/none/skipped_not_allowed) }` | 暂停:实例不再接新任务(须是该项目活跃成员,原有规则)+ 暂停它的托管连接(仅 super_admin,ADR-0047 §2,下一次调用即被拒);哪一半没执行、为什么见返回值;经 `can(hosted_runtime.operate)`(本人主登录或本人 MCP Token,账号有效);拒绝:`forbidden` / `session_refused` / `not_found` / `not_project_member` / `stopped`;scope `admin:monitoring`(v0.26.2676) |
| `resume_hosted_ai` | `id` | 同 `pause_hosted_ai` | 恢复:实例重新接任务、连接解除暂停;规则同上(v0.26.2676) |
| `stop_hosted_ai` | `id` | 同 `pause_hosted_ai` | 停用连接:实例隔离(不接新任务、运行环境不复用)+ 连接暂停;吊销凭据仍由添加人在「我的 Agent」里断开;规则同上(v0.26.2676) |
| `purge_hosted_ai_ghosts` | — | `{ schema, ok:true, candidates, destroyed, skipped }` | 清理:销毁没有项目、从没连上过(无节点 / 运行环境 / 心跳 / 运行)的空记录,每次最多 500 条,逐条加锁复核,不是纯空记录就跳过;不可恢复;先用 `list_hosted_ai` 看 `ghost_count`;经 `can(hosted_runtime.operate)`;scope `admin:monitoring`(v0.26.2676) |
| `set_my_display_name` | `name` | `{ name, changed }` | 本人改名：去空白后 1–40 字符，拒绝保留/不可见名称，每天最多 5 次；审计，稳定身份不变；scope `write:profile` |
| `set_my_fluxdesk_id_discovery` | `discoverable`、`idempotency_key`、`dry_run?` | `{ discoverable, changed, dry_run }` | 隐私 › 允许别人通过 ID 找到我(默认开启);关闭后按 ID 找不到你,邮箱/二维码与 @ 不受影响;当前值见 `get_my_profile.fluxdesk_id_discoverable`;拒绝 Agent 身份与 Project token;scope `write:profile`(v1.40) |
| `get_contact_remark` | `person_id` | 本人的私密备注、真实姓名与版本 | 仅本人已接受的好友，Project 凭据拒绝；scope `read:chatter` |
| `search_my_contacts` | `query` | 最多 50 个本人好友的名称/备注匹配 | 仅在已授权好友集合内搜索，不发现陌生人；Project 凭据拒绝；scope `read:chatter` |
| `set_contact_remark` | `person_id`, `remark`, `expected_remark` | 本人的更新后备注 | 1–40 字符，空字符串清除；CAS 冲突、重复写幂等，不改身份/提及/授权；Project 凭据拒绝；scope `write:chatter` |
| `get_my_ai_assistant` | — | `{ schemaVersion, homeProjectId, assistant: { id, name, paused, hasPersona, defaultModel, dailyQuota, skillCount, configUpdatedAt, hostedConnectionIds } \| null, memory: [{ id, type, title, body, projectId, projectName, updatedAt }], aiToolAccess: { connected }, otherAssistantCount, otherAssistants: [{ id, name, projectId, projectName, projectSlug }], hostedAvailable, hostedRequest, recentlyCancelled, projects: [{ id, name, slug }] }` | 我的 → Agent 与授权:只读真实归属;个人 placement + 本人创建是 Home 正向证据（其他项目/撤销/测试身份仍排除）；其他候选还需本人的有效 Home 绑定,非撤销/house/测试/示例/系统连接身份;其他项目仅计本人添加且双方仍是有效成员的活跃助手,按项目分组,按身份去重;托管能力来自存活通用节点,hostedRequest 仅实际请求;recentlyCancelled 只来自 cancelguard,不自动恢复;全部派生字段,读取从不写入;memory 来自项目时只在本人仍是成员时可见;projects 是授权了助手且本人仍在的项目;单 Project Token 回 agent_token_refused,其他 Agent 回 forbidden;scope read:meta |
| `list_my_ai_connections` | `operation_id?`(核对一次网页连接) | `{ connections: [{ id, client, project, capabilities, status, lastUsedAt, createdAt }], operation? }` | 我的 → Agent 与授权 → 接入我的 Agent:只列自己的连接,从不返回密钥;新连接只在登录后的网页上完成;scope `read:meta`(v1.34) |
| `list_my_agent_connections` | — | `{ connections: [{ id, kind(external/hosted), origin(oauth/token/hosted), vendor, name, access(read/write), project(null = 全部项目), state(active/expired/suspended/revoked), expiresAt, lastUsedAt, createdAt }] }` | 我的 → Agent 与授权 → 我的 Agent:只列自己的连接,从不返回密钥;接入的 Agent 或单 Project Token 调用回 `agent_token_refused`;scope `read:meta`(v1.37) |
| `get_my_agent_connection_activity` | `connection_id` | `{ activity: [{ tool, ok, errorCode, at }] }`(最近 50 次) | 自己某个 Agent 的调用记录,从不含参数或结果;不是自己的连接回 `not_found`;scope `read:meta`(v1.37) |
| `search_personal_contacts` | `query`(完整邮箱或完整 FluxDesk ID,均精确匹配、不区分大小写;不接受姓名或部分内容) | `{ results: [{ person_id, name, status(`none` = 还不是联系人), shared_project_names[≤3] }] }`(≤ 20) | 找人:找到这个账户本人,不需要共同 Project;ID 只找到开启「允许别人通过 ID 找到我」的人,旧 ID 不匹配;找不到、已停用、本人,或尚无关系且被拉黑(任一方)都是空结果(已有联系人保持原有的隐去状态);每天最多 30 次邮箱、20 次 ID 查询(没找到也算);从不返回邮箱或 ID;v1.40 起只限本人个人 Token(Agent 身份与 Project token 不可用);scope `read:chatter`(v1.34,v1.40 起含 ID) |
| `send_friend_request` | `fluxdesk_id` 或 `email`(二选一)、`note?`、`idempotency_key`、`dry_run?` | `{ found, sent, dry_run, person_id, name, status, outcome, reason, attribution }` | 发送好友申请:与 `search_personal_contacts` 同一查找(同样计次)和与 App 同一申请命令,需对方同意;拉黑、30 天内被拒、每天 3 次、1000 好友上限会拒绝(`outcome: denied` + `reason`);`dry_run` 查找但不发送;同一 `idempotency_key` 对同一人重试返回第一次结果;只限本人个人 Token,审计「AI 代 <本人>」;scope `write:chatter`(v1.40) |
| `get_personal_call_guard` | `user_id` | `{ state(shared/invite/create/unavailable), shared: { project_id, project_name } \| null, projects: [{ project_id, project_name, pending_invitation_id \| null }], can_create_project }` | 私聊里点「通话」被拦时的下一步:已有共同 Project → 去那里通话;否则列出我能加人且对方不在的 Project(带我已发出的邀请)或只能新建;没有关系/已停止/已拉黑 → `unavailable`。只限本人个人 Token;scope `read:chatter`(v1.35) |
| `list_project_member_invitations` | `project_id`, `include_candidates?`, `query?`(≤ 80) | `{ project_id, can_invite, can_remove, access_levels, pending: [{ invitation_id, user_id, name, sent_by_me, expires_at }], candidates: [{ user_id, name, state(available/member/pending) }] \| null }` | 一个 Project 等待接受的邀请,以及我能否邀请/移除(与成员页同一规则);`include_candidates` 时列出我的联系人与共同 Project 同事(≤ 200,停止/拉黑/安全限制的人不出现)。只限本人个人 Token;scope `read:chatter`(v1.35) |
| `get_meeting` | `project_id`, `meeting_id` | `{ ok, schema_version: "meeting-read/1.0", meeting_id, project_id, version, title, description, phase, starts_at, ends_at, duration_minutes, location, meeting_url, meeting_url_legacy, organizer: { user_id, name }, participants: [{ user_id, name, response }], linked_work_item, can_edit }` | 与 App 会议详情同一可见性：该 Project 的 live 成员，且是组织者、受邀人或持 `meetings.manage`；不可见返回 `not_found`。`version` 即 `update_meeting` 的 `expected_version`；`meeting_url` 按编辑规则返回 http(s) 链接；规则之前存下、现行规则会拒绝的旧链接(如「腾讯会议 123 456」、`zoommtg://…`、IPv6 字面量)按原样返回并带 `meeting_url_legacy: true`(原样重发即保留不动)，脚本协议的存值返回 `null`。scope `read:projects` 或 `write:meetings`；Project-scoped token 不可用。 |
| `get_meeting_management` | `scope: project \| workspace`, `project_id: UUID \| null`, `meeting_id`, `intent_id?`, `guest_links_cursor?` | `{ ok, view, receipt }` (`meeting-management/1`) | 与原生外部访客/会议资料同一查询；100条访客链接 keyset 分页、等候访客、当前文件和原操作回执。Project token 只能匹配其项目；workspace 只允许个人凭据和既有会议管理权限。scope `write:meetings`；不授予 Agent 人类治理身份。 |
| `command_meeting_management` | 同一 `scope/project_id/meeting_id`；`command` 含 `intentId`、`expectedMeetingVersion`、动作字段；`files?`(≤5个，base64，沿用文件限制) | `{ ok, view, receipt }` | 创建/撤销访客链接、放行等候访客、上传/替换/移除会议资料。链接创建不发送邮件；替换绑定当前附件 ID。Project/角色/文件扫描规则不变，workspace 不接受资料写入。未知结果查询原 `intent_id`；效果与成功回执原子提交。合同见 `Native_Meeting_Management_1024.md`。 |
| `get_owner_intake_templates` | `owner_email?` 或 `owner_user_id?` | `{ owner, description, description_source, checklists, checklist_source }` | 给别人创建需求前读取系统固定起稿和负责人的需求模板；`description.label` / `description.body` 只是可改写起点，负责人维护的是 `checklists` 模板；负责人未维护模板时返回系统默认模板。scope `read:tasks` |
| `list_docs` | `project_slug?`, `space_slug?`, `status?`, `kind?`, `limit?` | `{ count, docs: [{ id, title, slug, space_slug, project_slug, kind, status, updated_at, url }] }` | 列出当前 token 可见的 FluxDocs 文档(含项目隔离 + 草稿可见性) |
| `get_doc` | `page_id` 或 `(space_slug + page_slug)` | `{ id, title, body, slug, space_slug, project_slug, kind, status, updated_at, published_at?, url }` | 读取单篇 FluxDocs 文档正文 |
| `search_docs` | `query`, `project_slug?`, `space_slug?`, `status?`, `kind?`, `limit?` | `{ query, count, hits: [{ id, title, slug, space_slug, project_slug, kind, status, snippet, matched_field, url }] }` | 搜索 FluxDocs 标题 / 正文 |
| `list_doc_drafts` | `project_slug?`, `space_slug?`, `limit?` | `{ count, drafts: [{ page_id, title, slug, space_slug, project_slug, kind, author_name, updated_at, url }] }` | 列出当前 token 按权限可见的文档草稿。作者看自己草稿；admin / assistant / lead 看其范围内草稿 |
| `search` | `query: string`, `types?: ("feedback"\|"task"\|"meeting"\|"notification"\|"comment")[]`, `filters?: {project_slug,author_email,since,status}`, `limit?: number` | `{ query, count, hits: [{ type, id, title, snippet, matched_field, url, score, createdAt }] }` | 跨表 full-text search (v1 = ILIKE);scope = 所搜类型对应的任一读 scope(`read:feedback` / `read:tasks` / `read:inbox` / `read:projects` / `read:docs` / `read:chatter` / `read:audit` / `read:*`);只返回 token 能读的类型(v1.37) |
| `list_inbound_tokens` | `kind?: "personal" \| "moderation" \| "tools_claim"`, `include_revoked?: boolean`(默认 `false`) | `{ items: [{ id, label, kind, owner_email, created_at, revoked_at, linked_catalog_slug }] }` | Admin-only · scope `admin:catalog`。给 `broadcast_event.tools_token_id` 取 UUID 用,不暴露 token 原文/hash(Charter Art 7)。**inbound/admin legacy 面** —— 列的是个人入站(personal inbound)token,**不是** Project Integration 的 pair token / project-scoped token;工具接入某 project 请走 [Project Integration Pair & Token Flow](./current/02_core_flows/20_Project_Integration_Pair_And_Token_Flow.md)(成员申请 → Owner/Admin 审批 → 一次性 pair token → consume → project-scoped token) |
| `list_approvals_for_user` | `email`, `status?: "approved"\|"revoked"\|"pending"\|"all"`(默认 `approved`) | `{ items: [{ approval_id, catalog_slug, catalog_name, status, created_at, decided_at, decided_by }] }` | Admin-only(v0.24.10,scope `admin:approvals`)。给 offboard CLI 枚举一个用户的真实接入清单用,替代 fd1 之前的硬编码 catalog 列表(新加 catalog row 后会漏)。`status='revoked'` 返回 terminal 行(withdrawn/rejected/cancelled)。Charter Art 7:admin 本来就能查同样数据,只是 aggregation 便利;不返任何 token 值 |
| `get_avatars` | `user_ids?: uuid[]`(≤ 200;省略 = 自己) | `{ contract: "person-avatar/1.0", avatars: { [id]: PersonAvatar } }` | 头像统一合同(ADR 107):`uploaded`(仅当调用人可见该人照片)> `generated` > `placeholder`;URL 为同源相对路径 `/media/avatars/...`,上传头像需调用人自己的凭据读取。scope `read:meta` |

### 写类(state-changing)

| Tool | 输入 | 返回 | 守底 |
|---|---|---|---|
| `append_decision_outcome` | `decision_id`, `idempotency_key`, `assessment?`(as_expected/better/worse/mixed/unclear), `note?`(≤ 2000), `refs?`(对象类型 + id，≤ 20；按原样保存、不解析，读者各自按权限打开) | `{ ok, replayed, step }` | 给决策追加「结果」一步(发生了什么 + 证据引用)；不改决策本身；Project 在职成员(含 Agent)。scope `write:decisions` |
| `append_decision_review` | `decision_id`, `idempotency_key`, `assessment`, `note?`, `refs?` | `{ ok, replayed, step }` | 追加「复盘」一步；只有该决策的作者 / 决定者本人、非 Agent token。scope `write:decisions` |
| `set_decision_fields` | `decision_id`, `idempotency_key`, `confidence?`(low/medium/high\|null), `expected_outcome?`(≤ 500\|null), `review_at?`(ISO-8601\|null) | `{ ok, replayed, step }` | 设置/清除决策的把握、预期结果、复盘日，记为一条「修订」步(只记字段名)；只有作者 / 决定者本人、非 Agent token。没有任何 MCP 工具能替人做决定。scope `write:decisions` |
| `respond_personal_contact_request` | `person_id`, `action`(accept/decline/withdraw), `client_command_id?`(同 id 重放首个结果), `contact_period?`(仅 accept:`temporary_90d` 默认 / `temporary_7d` / `long_term`) | `{ person_id, action, outcome, reason, client_command_id, conversation_id }` | 接受/拒绝收到的好友请求,或撤回自己发出的;与网页/App 同一条版本化、幂等命令;只限本人个人 Token;scope `write:chatter`(`contact_period` 自 v1.34) |
| `report_personal_contact` | `person_id`, `client_report_id?` | `{ person_id, report_id, outcome, client_report_id }` | 举报给自己发过好友请求或已有会话的人,交平台人工复核;不发送自由文字,不拒绝、不屏蔽、不通知任何人;每人同一时间一条未处理举报;只限本人个人 Token;scope `write:chatter`(v1.34) |
| `invite_project_members` | `project_id`, `user_ids`(1–20), `access?`(member_self 默认 / member_full), `client_command_id?`(同 id 重放) | `{ project_id, client_command_id, results: [{ user_id, outcome, invitation_id }] }` | 一次邀请多个已有账号加入 Project;对方在「新的朋友」里接受后才入组,接受时再核一次邀请人的加人权限与人数上限;每人每小时最多 60 个邀请;不占新用户邀请额度。只限本人个人 Token;scope `write:chatter`(v1.35) |
| `create_project_and_invite` | `name`(≤ 80), `user_id`, `client_command_id?` | `{ project_id, project_name, invitation_outcome }` | 「新建项目并邀请」:建一个普通 Project(我是管理员,受建项目额度约束)再向对方发同一邀请;同 id 重试不会建第二个。只限本人个人 Token;scope `write:chatter`(v1.35) |
| `remove_project_member` | `project_id`, `user_id` | `{ project_id, user_id, outcome(removed/not_found/forbidden/refused) }` | 与成员页「移除」同一规则:不能移除所有者、最后一位协调人或自己(回 `forbidden`),写审计并通知。只限本人个人 Token;scope `write:chatter`(v1.35) |
| `set_project_module_enabled` | `project_id`, `module_id`(`milestones` / `documents` / `feedback` / `quick_chat` / `meetings`,v0.26.2668;功能模块 `work_time` 工时,回执 `schema` = `project-feature-modules/1`), `enabled`, `dry_run?` (work_time only: dry_run previews without writes), `expected_version`, `operation_id`(UUID) | `{ schema, outcome, operation_id, module }`(同 `operation_id` 重放 `already_applied`;`version_conflict` = 期间被改,重读) | 开/关 Project 的可开关模块:关闭后从导航、概览、App 与对应工具隐藏,数据保留,再开启恢复。只限 Project 管理员/协调员,重新核对成员身份;只限本人个人 Token(Agent / Agent 绑定 / 单 Project Token 回 `agent_token_refused`);新 scope `write:projects`(v1.34) |
| `reorder_project_modules` | `project_id`, `module_ids[]`(已安装模块的新顺序), `expected_version`(= get_project_modules `navigation.version`), `operation_id`(UUID) | `{ schema, outcome, operation_id, navigation }` | 功能与页面 › 导航顺序:改 Project 的模块顺序,所有没有自定义顺序的成员都按它显示;结果不明时调 `check_project_module_operation`(不带 `module_id`)并读回 `navigation`;只限管理员/协调员,本人个人 Token;scope `write:projects`(v0.26.2668) |
| `rename_project_module` | `project_id`, `module_id`, `display_name`(1–12 字;null/空 = 恢复默认), `expected_version`, `operation_id` | `{ schema, outcome, operation_id, navigation }` | 给模块起 Project 显示名(例如 工作项 → 待办);记录标题不变;设置不能改名;`invalid_name` / `duplicate_name`;结果不明时同 reorder 用 `check_project_module_operation`(不带 `module_id`);只限管理员/协调员,本人个人 Token;scope `write:projects`(v0.26.2668) |
| `switch_project_template` | `project_id`, `template`, `expected_version`(= 预览 `current.version`), `expected_composition?`(= 预览 `composition`), `operation_id`(UUID), `dry_run?` | `{ schema, project_id, operation_id, state, outcome(applied/already_applied) }` | 更换模板:按预览安装/移除模块、写顺序与名称,已有数据全部保留;预览后模块被改过则 `version_conflict`;结果不明时用 `check_project_template_operation`;经 `can()`,只限管理员/协调员本人 Token;scope `write:projects`(v0.26.2668) |
| `save_project_as_template` | `project_id`, `name`(1–40), `include{ modules, order_and_names, roles, starter_items }`, `visibility(private/organisation)`, `organization_id?`, `operation_id` | `{ outcome, template }` | 另存为模板(仅自己 / 我的组织);本人个人 Token;scope `write:projects`(v0.26.2668) |
| `create_project_from_template` | `name`, `template`, `slug?`, `description?` | `{ project_id, slug, name, template }` | 从模板新建 Project(个人空间只有本人);scope `wms:write` + `admin:catalog`(v0.26.2668) |
| `manage_saved_project_template` | `template_id`, `expected_version`, `action(rename/delete)`, `name?` | `{ template }` | 改名或删除自己保存的模板;scope `write:projects`(v0.26.2668) |
| `milestone_create` | `project_id`, `name`(≤120), `description`(可验证的结果,必填,≤2000), `starts_on`, `target_end_on`(YYYY-MM-DD), `status?`, `client_message_id`(UUID), `dry_run?` | `{ schema, outcome(applied/would_apply/replayed), dry_run, acting_as, milestone }` | 同网页「新建里程碑」:只限 Project 负责人(owner/lead);审计 `milestone.created` 与网页同一行,另记 MCP 归属;通知项目成员,署名「<AI> 代 <本人>」;同 `client_message_id` 重放返回同一里程碑,内容不同回 `conflict`;`dry_run` 只检查不写入;scope `write:tasks` 或旧 `write:projects`（任一即可）(v1.39) |
| `milestone_update` | `project_id`, `milestone_id`, `expected_version`, `name?`, `description?`, `status?`, `starts_on?`, `target_end_on?`, `client_message_id`, `dry_run?` | `{ …, milestone, previous }` | 只改给出的字段;只改日期 = 网页路线图改期(审计 `milestone.rescheduled`,不通知),否则同网页编辑(审计 `milestone.updated`,通知成员);`previous` 可用于改回;`version_conflict` = 期间被改,重读;只限 Project 负责人;scope `write:tasks` 或旧 `write:projects`（任一即可）(v1.39) |
| `milestone_archive` | `project_id`, `milestone_id`, `expected_version`, `client_message_id`, `dry_run?` | `{ …, milestone, previous }` | 同网页「归档」:不再出现在列表与选择器,关联工作项保留关联;目前没有恢复;AI 调用固定返回 `proposal_only`,由本人在项目「里程碑」归档;只限 Project 负责人;scope `write:tasks` 或旧 `write:projects`（任一即可）(v1.39) |
| `milestone_items_set` | `project_id`, `changes[]`(≤50,`{ work_item_id, milestone_id\|null, from_milestone_id? }`), `client_message_id`, `dry_run?` | `{ schema, outcome, dry_run, acting_as, results: [{ work_item_id, outcome(applied/unchanged/would_apply/refused), error_code?, message?, from_milestone_id, to_milestone_id }] }` | 把工作项加入里程碑、移出(`null`)或移到本 Project 另一个里程碑;规则同工作项页的里程碑字段(Project owner/lead/PO/TL);工作项与里程碑都必须属于 `project_id`(否则 `not_found` / `validation_failed`,不跨 Project);`from_milestone_id` 与当前不符回该项 `version_conflict`;逐项结果,一项被拒不影响其他;审计 `task.milestone_set` 与网页同一行;scope `write:tasks` 或旧 `write:projects`（任一即可）(v1.39) |
| `save_ai_assistant_memory` | `source`(`owner` / `project`), `project_id?`(source=project 时必填), `type?`(user / feedback / project / reference), `title`(≤200), `body`(≤4000) | `{ memoryId, placement, projectId }`(`placement`:`home` 或 `project`) | 给你的 Agent 存一条记忆,来源由调用方声明:`owner`(你自己的偏好、指示、参考)留在我的空间;`project`(从那个项目读到的内容,必须这样声明)留在那个项目,离开项目就随之删除,绝不复制进我的空间。不幂等:结果未知时先 `get_my_ai_assistant` 查一遍再决定。错误:`invalid_input`、`assistant_unavailable`、`project_unavailable`(那个项目没授权助手或你已不是成员)、`limit_reached`(50 条)、`forbidden`、`agent_token_refused`;scope `write:profile` 或 `agent:dispatch`(v1.36) |
| `stop_my_ai_connection` | `connection_id`, `confirmed:true` | `{ connectionId, outcome }`(已停用回 `already_stopped`) | 停用自己的一个 Agent 连接,其凭据立即失效;Agent / 单 Project Token 不能停用(`agent_token_refused`);scope `write:profile`(停用是写操作,只读 Token 不能停用;v1.34) |
| `revoke_my_agent_connection` | `connection_id`, `confirmed:true` | `{ connectionId, outcome }`(`revoked` / 已断开回 `already_revoked`) | 断开自己的一个 Agent,其凭据立即失效;幂等,结果未知时用 `list_my_agent_connections` 读 `state`;接入的 Agent 或单 Project Token 不能断开(`agent_token_refused`);scope `write:profile`(v1.37) |
| `mark_notification_read` | `notificationId` | `{ updated: boolean }` | 只能改自己的 |
| `mark_all_notifications_read` | — | `{ updatedCount }` | 只清自己的 |
| `submit_daily_update` | `note?`, `content?`(deprecated alias), `availability_status?`, `availabilityStatus?`(deprecated alias), `mainTaskIds?[]`(deprecated WorkItem references), `contactWindow?`(deprecated alias), `blockers?`, `mood?`(legacy metadata), `project_codename?`(visible context only) | `{ daily_id, submitted_at, was_update }` | 写入 canonical Daily Sync；按用户本地日更新。`mainTaskIds` 只更新 Daily Sync→WorkItem 引用，不会修改 WorkItem 负责人、进度或状态；旧 `daily_updates` 仅作历史读取回退 |
| `create_task` | **只需 `title`**。`project_id?` / `project_slug?`(slug 或项目名;都不填时:只给自己的待办放进自己的「我的空间」(首次使用时自动创建;`project_slug: "project0"` 也指它);有父工作项时用父工作项所在的 Project;指定了别人(执行人、审核人、抄送)或没有我的空间时,只有一个可创建的团队 Project 就直接用,多个返回 `needs_choice`(「我的空间」从不作为候选)), `due_date?`(`YYYY-MM-DD` = 你所在时区当天结束;不带偏移的日期时间如 `2026-10-24T15:00` 按 **Project 时区**理解;带偏移以偏移为准), `description?`, `parent_id?` / `parent_task_code?`, `assignee_email?`, `assignee_user_id?`, `reviewer_user_ids?[]`(≤50), `cc_emails?[]`, `cc_user_ids?[]`, `priority?`, `kind?`, `visibility?`(`project` / `participants`,默认 `project`), `idempotency_key?`, `as_draft?` | 创建:`{ task_id, code, url, assignee, priority, visibility, cc, reviewers, delivery, project_id, project_name, project_inferred, due_at, web_url, app_url, summary }`;没定下项目:`{ ok: true, status: "needs_choice", created: false, choice: "project", field, candidates: [{ project_id, name, slug }] }`(≤10 个,最近活跃在前) | 走标准创建路径,带 audit。候选只包含调用人自己有效成员身份的 active Project(项目级 token 只会是它那一个),推断不扩大可见范围;非成员 Project 仍返回 `not_found/project_unavailable`;一个 Project 都没有返回 `validation_failed/project_required`。`summary` 是一句人话(「已创建待办「…」，截止 …（项目 X）」),`app_url` 为 `<App 链接前缀>://projects/{projectId}/work-items/{id}`(前缀见 v1.33:my.fluxdesk.net `fluxdesk-familiar`,TEST `fluxdesk`,app.fluxdesk.net 不返回)。Project Agent 凭据仍不能直接创建(`human_adoption_required`)。`visibility="participants"` 贯穿 draft / proposal / human adoption / direct-create / idempotency。给别人建单时先调 `get_owner_intake_templates`。`assignee_user_id` / `assignee_email` 设非自己时需分派权限,否则 `{ error: { type: "forbidden", title: "permission_denied", ... } }`(不静默 fallback);`assignee_user_id` 优先于 `assignee_email`。 |
| `create_meeting` | **只需 `title` + `starts_at`**。`project_id?` / `project_slug?`(slug 或项目名,推断与 `needs_choice` 同 `create_task`;有关联工作项时用它所在的 Project,指定了参会人时不用「我的空间」), `task_id?` 或 `task_code?`(可选,必须属于同一 project;两者同时传入时必须指向同一工作项), `starts_at`(`2026-10-24T15:00` 按 Project 时区理解,传了 `timezone` 则按它;带偏移以偏移为准), `timezone?`(IANA), `duration_minutes?`(5-480,默认 30), `participant_names?[]`(按名字只在该 Project 当前成员里找), `participant_user_ids?[]`, `participant_emails?[]`, `description?`, `location?`, `meeting_url?`, `mode?`(`scheduled`/`huddle`), `start_now?`, `idempotency_key?` | `{ meeting_id, url, huddle_url, title, project_id, task_id, task_code, starts_at, ends_at, mode, participants, project_name, project_inferred, timezone, replayed, web_url, app_url, summary }`;项目或人名不唯一时 `{ ok: true, status: "needs_choice", created: false, choice: "project" \| "participant", candidates }` | scope `write:meetings`。只允许项目内会议:调用者必须能看到该项目,参会人必须属于同项目,`task_id` / `task_code` 必须属于同一项目。人名不唯一 → `needs_choice`(候选 `{ user_id, name, email }`);找不到(含项目外的人)→ `not_found/participant_not_found`,从不在项目外找人。不填参会人 = 只给自己的日程(仅限 token 本人直接创建;Project Agent 提案仍需至少 1 个非创建者参会人)。`idempotency_key` 直接创建时同样生效:同 key 同请求返回同一会议(`replayed: true`,不重发邀请),同 key 不同请求返回 `conflict/idempotency_conflict`。创建者同一时间段内已有自己创建的未取消会议时返回结构化 `conflict/creator_time_overlap`;`meeting_url` 只接受 http(s)(无协议补 https://),否则返回 `validation_failed/meeting_url_invalid`;写 audit + 邀请通知。`summary` 例:「已创建：10月24日 周五 15:00–15:30 与 Murphy 的会议「过一下发布」（项目 X）」;`app_url` 为 `<App 链接前缀>://meetings/{id}`(同上)。 |
| `prepare_meeting_link` | `project_id`, `title`, `description?`, `participant_user_ids?[]`(允许空列表), `duration_minutes?`(默认 30，5-480 且受创建者不可变策略上限约束), `expires_at`, `idempotency_key` | human direct lifecycle receipt；Project Agent `pending_human_confirmation`；direct lifecycle 结果统一返回 duration policy/max/selected、room started/deadline/ended 与 lobby accepting | `meeting.prepare_link/1.0`;创建同一 canonical Meeting，不伪造时间、不创建 Huddle/Decision Link/日历事件。Project Agent proposal 与 human direct 路径保留同一 duration 输入；确认前不产生 Meeting/bearer。`get/revoke/expire/convert_meeting_link` 返回同一 duration/room/lobby 结果形状。缺失或关闭 `meetings.link_ready_writes` 时 direct/adoption 都拒绝。Project Agent 提案的 scheduled `create_meeting` 仍要求至少一名非创建者参会人(token 本人直接创建可以只给自己排日程)。 |
| `get_meeting_link` | `project_id`, `meeting_id` | 同上 | 仅当前 live Project member 且为原 Meeting creator 可读；返回的 `access_path` 是 bearer capability，不得写日志或转存到公开位置。 |
| `revoke_meeting_link` / `expire_meeting_link` | `project_id`, `meeting_id`, `expected_version`, `idempotency_key` | 同上，`link_state` 为 `revoked` / `expired` | creator-only、乐观版本、幂等 replay；不删除 Meeting。 |
| `convert_meeting_link` | `project_id`, `meeting_id`, `expected_version`, `idempotency_key`, `starts_at`, `duration_minutes` | 同一 `meeting_id`，`schedule_state: "scheduled"`, `link_state: "converted"`, `access_path: null` | 在同一 Meeting row 上补充真实时间并消费 bearer capability；不创建第二场 Meeting。revoke/expire/convert 互斥并由 row lock 串行。 |
| `update_meeting` | `project_id`, `meeting_id`, `expected_version`(会议版本，用 `get_meeting` 读取), `idempotency_key`, 以及至少一项: `title?`, `description?`(议程，`null` 清空), `location?`, `meeting_url?`, `task_id?`(`null` 解除关联), `starts_at?` + `duration_minutes?`(改期须同时给出，5-480), `reschedule_note?`, `add_participant_user_ids?[]`, `remove_participant_user_ids?[]` + `removal_reason`(移除时必填) | `{ ok, schema_version: "meeting-update-receipt/1.0", command_id, meeting_id, project_id, expected_version, version, applied: { details, schedule, added_participant_ids, removed_participant_ids }, replay }` | scope `write:meetings`。与 Web 编辑面板、App「编辑」同一 `meeting-update-command/1.0`：只有组织者或在该 Project 内持 `meetings.manage` 的 live 成员可改；已取消/已结束会议返回 `conflict/meeting_closed`；`expected_version` 过期返回 `conflict/version_conflict` 并在 `error.current_version` 给出当前版本；同一 `idempotency_key` 重放返回原回执(`replay: true`)且不重复通知，换内容则 `conflict/idempotency_conflict`。改期重置 RSVP、ICS SEQUENCE+1 并通知其他参会人；新参会人必须是该 Project 的 live 成员(否则 `participant_not_allowed` 且整条命令回滚)；组织者不能被移除；先处理移除，被移除的人只收到移除通知；改动后的 `meeting_url` 只接受 http(s)(无协议时补 https://，最长 500 字符，其它协议返回 `meeting_url_invalid`)，原样重发已存的旧链接(任意长度)则保留不动，但 `javascript:` / `data:` 等脚本协议即使未改也拒绝；能看到会议但无权编辑返回 `forbidden`，看不到返回 `not_found`。Project Agent 凭据不可调用(`forbidden/meeting_update_requires_human`)。 |
| `claim_task` | `code` 或 `task_id` | `{ task_id, code, status, assignee, url }` | 认领未分配的 `issue` task。调用者必须是项目成员 / admin / coordinator。成功后写 `task.claimed` audit 并通知 creator + PO/TL |
| `respond_to_workitem_assignment` | `project_id`（令牌未绑定 Project 时**必填**，否则返回 `project_scope_mismatch`；绑定时可省略，给出则必须一致）, `work_item_id`, `expected_work_item_version`, `command_id`, `idempotency_key`, `response`: `accept` / `ask` / `decline`; 可选 `effort_estimate_hours` 与 `revision_reason`（accept；省略工时 = 保留当前预计工时，给出不同的新值且已有值时需 `revision_reason`）、必填 `question`（ask）、可选 `reason`（decline） | `{ ok, response, command_id, replayed, audit_id, truth, notification_delivery? }` | 被邀请的真人执行者回复接手邀请（2026-10-05 fix1005，与 Web 同一 canonical 命令，见 `02_Work_Contract_Protocol.md`）。accept = 已接手·待开始（不开工）；ask = 本人写下的问题进入工作项沟通，仍未接手；decline = 本次邀请结束，工作项保留（无执行者）。三者都告知邀请人（最近一次把工作指派给你的人，否则创建者）。没有回复时限。`ai_agent` 身份拒绝；按令牌 Project 收敛并每次重新鉴权。scope `write:tasks` |
| `close_work_item` | `project_id`（必填）, `work_item_id`, `reason`（必填，1–500 字）, 可选 `idempotency_key` | `{ ok, outcome: "closed", replayed, audit_id, cancelled_dispatch_ids, still_running_dispatch_ids, notified_user_ids, truth }`；`truth.counts_as_done` 恒为 `false` | 关闭（取消）一个不再需要的工作项（不做了 / 建错了），与 Web「关闭这个工作项」同一 canonical 命令（`workitem-close-command/1.0`，见 `02_Work_Contract_Protocol.md`），也可走 `POST /api/v1/projects/{projectId}/work-items/{workItemId}/close`。**关闭 ≠ 完成**：执行状态保持原样，任何「已完成」统计都不计入；已完成的工作项返回 `already_done`。只有创建人或该 Project 的负责人（owner/lead）可关闭，viewer、只读成员、非成员、其他 Project 一律拒绝，每次在服务端重新校验。原因写在工作项上并进历史；当前执行者（不是关闭者本人时）收到通知；排队中的助手派发被取消，正在运行的如实列在 `still_running_dispatch_ids`。已关闭再关返回 `already_closed`；带同一 `idempotency_key` 重试返回原结果（`replayed: true`），同 key 换内容返回 `idempotency_conflict`。`ai_agent` 身份拒绝；Project 绑定的 Agent 令牌看不到此工具。scope `write:tasks` |
| `submit_work_item_delivery` | `project_id`（令牌未绑定 Project 时必填）, `work_item_id`, `expected_work_item_version`, `command_id`, `idempotency_key`, `result`(1–2000), `limitations`(1–1000，没有请写「无」/None), `evidence_refs?[]`: `{ url, criterion_key }`(≤10，HTTPS、不含账号密码，`criterion_key` 取自 `get_work_item_delivery_rounds.criteria`) | `{ ok, command_id, replayed, delivery: { id, round }, truth }` | 2026-10-16 delivery176：已接手的真人执行者提交结构化交付（executor-step 1.1，与 Web/App 同一命令），进入待验收（无验收人时直接完成）。链接只存引用、不抓取不验证；同一 key 不同内容 `idempotency_conflict`，原样重发返回原回执。`ai_agent` 身份拒绝（Agent 用 `submit_delivery_evidence`）。scope `write:tasks` |
| `submit_work_item_delivery_supplement` | `project_id?`, `work_item_id`, `obligation_id`(=`openEvidenceRequest.obligationId`), `expected_work_item_version`, `command_id`, `idempotency_key`, `note`(1–1000), `evidence_refs?[]` | `{ ok, replayed, round, reference_count, receipt }` | 回应验收人的「要求补证」：同一轮次，工作项仍待验收，验收人再判断。一次请求只能回应一次；`ai_agent` 拒绝。scope `write:tasks` |
| `get_work_item_delivery_rounds` | `work_item_id` | `workitem-delivery-rounds-read-model/1.0` | 完成标准（带 key）、每一轮交付（结果、局限、对应标准的依据、补证请求与回应、唯一的终局决定）、未回应的补证请求、调用者可做什么、验收结果与来源同步分开（只有 `applied` 表示来源已确认）。需要 Project 的有效成员身份。scope `read:tasks` |
| `update_task_status` | `code: "REQ-1234"` 或 `task_id: uuid`(v0.20.29 起 `code` 为 canonical;`task_code` 仍接受为 deprecated alias),`status`, `progress_note?` | `{ task_id, code, status, new_status, comment_id }`(v0.20.29 起 `status` 为 canonical;`new_status` 仍输出为 deprecated duplicate,下个 release 移除) | 只允许该工作项所在 Project 的活跃成员且为 assignee(review→done/in_progress 走验收判断);平台角色(admin/super_admin/hr_admin)本身不授权,非成员与不存在的工作项同样返回 `task not found`;无 Project 的工作项保留平台兜底;不对 Project 助手开放(需要时用 `ask_human`)(v1.37) |
| `add_task_comment` | `taskId`, `body` | `{ commentId }` | 评论,带 audit;不对 Project 助手开放(需要时用 `ask_human`)(v1.37) |
| `add_feedback_comment` | `feedbackId`, `body`(≤ 8000 字符,v0.19.5 起;原 4000),`client_message_id?` | `{ commentId }` | 同上;错误信封改用结构化 RFC 7807 形式,见下 § 8.1 |
| `create_feedback` | `title`, `body`(≤ 8000 字符,v0.19.9 起;原默默截断在 4999),`project_slug?`, `is_anonymous?`, `auto_delete_after?` | `{ feedback_id, url }` | project_slug 缺省 / "unsure" 进 反馈池 triage;超长 body 现在直接拒绝(结构化 RFC 7807 错误,见下 § 8.1)而非静默截断;scope `write:feedback`(`as_draft` 时 `write:feedback:draft`)(v1.37) |
| `execute_action` | `action_id`, `params?`, `context?{ action_context, project_id?, project_slug?, task_id?, chatter_id?, feedback_id?, doc_space_id? }` | `{ ok, actionId, handler, status(executed/needs_params), href?, missingParams?, targetType?, targetId?, result? }` | 经 Cmd-K 同一服务端 handler 执行 Action Catalog 动作;先 `list_actions` 看动作参数与 `requiredMcpScopes`。scope `read:tasks` 或 `wms:write`(至少持有一个可执行动作的 scope),之后再校验所选动作自己的 scope(v1.37) |
| `bind_project_repository` | `project_slug`, `provider: "gitone" \| "gitea"`, `repo_url`, `ssh_url?`, `default_branch?` | `{ repo_id, project_slug, provider, web_url, full_name, default_branch, created }` | 绑定自建代码库到项目；服务端解析 URL 并写 audit。**Token scope:** `code:repo:bind`（推荐）或 `admin:projects`（向后兼容）。**Per-project 授权（服务端）：** 必须是 platform super_admin、本项目 Project Lead、本项目 Tech Lead，或持有 `code.repo.bind` 能力 |
| `unbind_project_repository` | `project_slug`, `repo_url` | `{ repo_id, project_slug, archived: true }` | 归档项目↔代码库绑定，保留历史动态链接。Scope + 授权要求同 `bind_project_repository` |
| `create_doc_draft` | `project_slug?` 或 `space_slug?`(二选一至少一项), `title`, `slug?`, `body?`, `kind?`, `parent_page_id?`, `pinned?`, `change_summary?` | `{ page_id, project_slug, space_slug, slug, status: "draft", url }` | 创建 FluxDocs 草稿。`space_slug` 缺省时会自动复用 / 创建项目默认文档空间；可显式传 `slug` 避免中文标题落成泛化 URL |
| `update_doc_draft` | `page_id` 或 `(space_slug + page_slug)`, `title?`, `body?`, `kind?`, `parent_page_id?`, `pinned?`, `change_summary?` | `{ page_id, slug, space_slug, project_slug, status: "draft", url }` | 更新草稿内容。只允许作者改正文；published 文档不能走这条 |
| `publish_doc_draft` | `page_id` 或 `(space_slug + page_slug)` | `{ page_id, slug, space_slug, project_slug, status: "published", published_at, url }` | 人工确认后把草稿发布到文档中心。守同 web UI 发布权限(author/admin/assistant/lead) |
| `archive_doc` | `page_id` 或 `(space_slug + page_slug)` | `{ page_id, slug, space_slug, project_slug, status: "archived", archived_count, url }` | 下线文档；父文档归档时级联归档整棵子树 |
| `broadcast_event` | `recipients[]`(≤100), `event_type`, `severity`, `title`, `summary?`, `markdown_body?`, `actions?[]`, `event_id_prefix?`, `project_slug?`, `tools_token_id?`, `auto_claim?`(v0.20.0 起,默认 `true`) | `{ ok, delivered: [{ email, deliveryId, deduped, pushed, autoClaimed? } \| { email, error }] }` | Admin-only(非 admin 直接 `forbidden`)。v0.20.0 起:未 claim 的 recipient 默认会用 `tools_token_id` 内联 mint 一个 personal token 再投递(每个 +1 DB RTT),结果行带 `autoClaimed: true`;大批量 fan-out 建议先 `POST /api/v1/tokens/claim/batch`,再 `auto_claim: false`。Charter Art 2(no nag)+ Art 7(server-side claim)。**inbound/admin broadcast legacy 面 · 非凭证投递路径**:`broadcast_event` 已被 `PERSONAL_TOKEN_HIGH_RISK_DENYLIST`(`src/mcp/lib/project-scope.ts`)从所有 `/mcp` personal bearer token 剥掉(刻意安全收口),且**不得**用来投递 pair token / 密钥 / 任何一次性凭证 —— 站内信 body(`notifications.body`)会进 SSR、admin deliveries 页原文可读。凭证/pair token 请走 [Project Integration Pair & Token Flow](./current/02_core_flows/20_Project_Integration_Pair_And_Token_Flow.md) 的专用一次性 reveal/consume 端点 |
| `resolve_inbound_event` | `delivery_id`, `resolution_note?` | `{ ok, deliveryId, resolved, resolvedAt?, resolutionNote? }` | 只允许原发送人:写入该投递的 personal token 的 owner,或铸造该 token 的 tools-claim token 的 owner(带 `tools_token_id` 的 admin 广播),与 REST 规则一致;平台角色本身不授权,被拒与不存在同样返回 `delivery_not_found` |
| `supplement_inbound_event` | `delivery_id`, `markdown_body`(≤4000) | `{ ok, deliveryId, supplementId, createdAt }` | 只允许原发送人(规则同 `resolve_inbound_event`);平台角色本身不授权,被拒返回 `delivery_not_found`;Append-only,原 body 永不可变(Charter Art 9) |
| `issue_admin_tools_claim_token` | `label`, `source_note?`, `catalog_display_name?`, `catalog_description?` | `{ token_id, token_value, catalog_row_id, catalog_slug, daily_limit, endpoint_url }` | Admin-only(v0.22.2,scope `admin:tokens`)。包装 `createToolsClaimToken` 表单 action,mint `flux-tools-…` + 自动建对应 catalog row。`token_value` **只回一次**(Charter Art 7) |
| `issue_admin_moderation_token` | `label`, `source_note?`, `daily_limit?`(默认 200) | `{ token_id, token_value, catalog_row_id, catalog_slug }` | Admin-only(v0.22.2,scope `admin:tokens`)。包装 `createModerationInboundToken`,mint `flux-in-pers-…`(`is_moderation=true`)+ catalog row |
| `issue_personal_inbound_token` | `label`, `daily_limit?`(默认 50) | `{ token_id, token_value, endpoint_url }` | v0.22.2,scope `write:inbox`。**只给 caller 自己** mint(Charter Art 9 — 无 `target_user_id` 参数);包装 `createPersonalInboundToken`;个人 token 看不到此工具(`PERSONAL_TOKEN_HIGH_RISK_DENYLIST`)。2026-10-24:须先有已批准的个人接入权限(与网页表单同一闸门),否则 `inbound_integration_access_required` |
| `get_personal_inbound_access` | — | `{ access: "granted"\|"pending"\|"self_grantable"\|"requestable"\|"unavailable" }` | 2026-10-23,scope `read:catalog`。个人接入权限(`inbound-integration-access`,只提醒我自己)的状态,与网页「外部系统提醒我」同一读模型 |
| `request_personal_inbound_access` | — | `{ access: "pending"\|"granted" }` 或错误 `unavailable` | 2026-10-23,scope `write:inbound`。只为自己申请个人接入权限(资源目录自己的申请,管理员审批);2026-10-24 起:该资源目录行打开 `member_self_grant` 时,公司(组织)成员调用直接回 `granted`(`get_personal_inbound_access` 先回 `self_grantable`),无需等审批;等待中重复调用不重复提交。批准后申请人收到「已批准 · 生成 Token」,在已登录的「外部系统提醒我」(`/me/integrations/tokens`,App 同名页)自己生成;MCP 不返回凭证 |
| `revoke_admin_token` | `token_id`(uuid), `reason?` | `{ revoked: true }` | Admin-only(v0.22.2,scope `admin:tokens`)。包装 `adminRevokeInboundToken`,可吊销任意 `personal_inbound_tokens` row;idempotent;owner 收到 push + audit |
| `revoke_personal_token` | `token_id`(uuid) | `{ revoked: true }` | v0.22.2,scope `write:inbox`。只能吊销**自己** owner 的 row;idempotent;包装 `revokePersonalInboundToken` |
| `request_project_inbound_connection` | `project_id`(uuid), `name`(2–120), `purpose?`(≤200,一行) | `{ request_id, status: "pending" }` | 2026-10-22,scope `write:inbound`。Project 成员(不能自己添加外部来源的)向该 Project 的 owner / lead 申请接入外部系统;通知管理者,管理者批准或拒绝;2026-10-24 起批准不再给批准人任何密钥:申请人收到「已批准 · 生成 Token」,在 `get_project_inbound_connection_details` 的 `requested_by_you.generate_href`(已登录网页/App)自己生成;MCP 不批准、不返回密钥。会按人提醒每个人的系统(如 CrawlTrace)请改用 `request_personal_inbound_access`(只提醒我自己)。管理者自己调用回 `signal_request_manager_can_connect`;每人每 Project 最多 5 个待批准 |
| `list_project_inbound_connection_requests` | `project_id`(uuid) | `{ can_manage, pending[], mine[] }` | scope `read:projects`。`pending` 只对管理者返回该 Project 全部待批准申请;`mine` 为自己的申请(任意状态)。非成员 `signal_request_not_member` |
| `reject_project_inbound_connection_request` | `project_id`(uuid), `request_id` | `{ request_id, status: "rejected" }` | scope `write:inbound`。仅该 Project owner / lead,且须本人 token(Agent 身份 token 拒绝)。 |
| `cancel_project_inbound_connection_request` | `project_id`(uuid), `request_id` | `{ request_id, status: "cancelled" }` | 2026-10-22,scope `write:inbound`。申请人本人撤回自己仍在等待的申请(仍是该 Project 成员;须本人 token,Agent 身份 token 拒绝)。已处理的申请返回 `signal_request_not_pending`;他人的申请返回 `signal_request_not_found`。与 App「撤回申请」同一命令 |
| `get_project_inbound_connection_details` | `project_id`(uuid), `source_id?`(uuid) | `{ schema_version, can_manage, project{id,name}, sources[{ source_id, source_key, name, connector, enabled, endpoint, examples{curl,json}, requested_by_you{request_id, token_generated, generate_href}\|null }] }` | 2026-10-22 (B1.135 接入详情),scope `read:projects`。与网页来源卡「接入详情」同一读模型:接入地址取部署配置的可信公网 https origin + `/api/signals/inbound`(未配置时 `endpoint: null`、`examples.curl: null`),示例里 Key 只以 `${FLUXDESK_SOURCE_KEY}` 占位,**从不返回密钥**。管理者看该 Project 全部有效来源;其他成员只看由自己已批准申请创建的来源。非成员 `signal_request_not_member`;`source_id` 不可读/不存在 `signal_source_not_found`;2026-10-24:`requested_by_you` 标出由你本人的申请创建的来源,`generate_href` 是你生成 Token 的页面 |
| `prepare_project_inbound_connection_test` | `project_id`(uuid), `source_id`(uuid) | `{ test_id, ticket, mode: "dry_run"\|"delivery", source_id, source_name, credential_version, identity_evidence_at, expires_at, side_effects:false }` | 2026-10-22 (srcprobe 对接测试),scope `read:projects`。只读,与来源卡「对接测试」同一命令;Project owner / lead,活跃 Project,来源启用未吊销。有 B1.131 探针回执 → `dry_run`(示例事件);否则 `delivery`(等外部系统发一条测试消息)。`ticket` 10 分钟有效、不含密钥。错误 `signal_config_test_<forbidden\|source_unavailable\|project_inactive\|…>` |
| `run_project_inbound_connection_test` | `ticket` | `{ state:"recorded", receipt{test_id, source_id, mode, state, reason, destination, recorded_at, current} }` 或 `{ state:"waiting", expectation{test_id, source_id, started_at, expires_at, header, endpoint, curl} }` | 2026-10-22,scope `write:inbound`。同一 ticket 幂等。`delivery`:写一条仅元数据的期待(10 分钟),外部系统用真实 Key 向 `/api/signals/inbound` 发一条带 `X-FluxDesk-Test: <test_id>`(或 `?fluxdesk_test=`)的事件即被识别为测试:不入库为信号、不通知任何人、不建工作项;别的 Project / 来源的 Key 返回 404 `test_not_found`,过期 410 `test_expired`,Key 已轮换 409 `test_key_changed`。`curl` 里 Key 只以 `${FLUXDESK_SOURCE_KEY}` 占位,**从不返回密钥** |
| `check_project_inbound_connection_test` | `project_id`, `source_id`, `test_id`(uuid) | `{ state:"recorded"\|"waiting"\|"expired"\|"key_changed"\|"not_found", receipt?, expectation?, expires_at? }` | 2026-10-22,scope `read:projects`。按 test_id 查结果,从不执行。`recorded.receipt.current:false` = Key 已轮换,旧回执不再适用;`key_changed` = 等待中 Key 已轮换或来源已暂停,需重新开始。窗口过后带测试标记的请求一律 410(即使已有回执),避免遗留标记吞掉真实事件 |
| `create_project_inbound_connection` | `project_id`(uuid), `name`(2–120), `connector?`(custom\|grafana\|alertmanager\|sentry), `then?`(notify\|record\|confirm\|draft,默认 notify), `person_ids?`(≤5), `idempotency_key`, `dry_run?` | `{ replayed, source_id, system_id, state:"connected"\|"partial", routing_enabled, credential:"generate_on_web", generate_href }`;`dry_run` → `{ dry_run:true, would{…} }` | 2026-10-26 (connfollow-1026),scope `write:inbound`。**只限本人 token**:AI agent token、Project/助手绑定 token、OAuth 连接的 Agent、托管节点一律拒绝(`forbidden`,`reason: person_required`,dry-run 也拒绝;建议经 ask_human 让人来做)。与网页「连接系统」同一组命令:创建来源 + 收到后规则 + 规则生效;我的空间只有本人。**从不返回连接凭据**(生成凭据是仅限浏览器登录的 `credential.mint`):打开 `generate_href`(系统页 › 重新生成连接凭据)生成一次。`partial` = 已创建但 `failed_step` 未完成:`routing` → `activate_project_inbound_rules`;`rule`(收到后未保存)→ 在 `next_href` 页重新选择。同 key 同参数重放 `replayed:true`(重放前仍重新核对管理权限);同 key 不同参数 `idempotency_conflict`;`operation_in_progress` = 回答可能在连接已建好后丢失,先看 `list_project_connected_systems`,不要换 key 重建 |
| `activate_project_inbound_rules` | `project_id`, `source_id`(uuid), `active?`(默认 true), `idempotency_key`, `dry_run?` | `{ replayed, source_id, system_id, name, outcome, state, routing_enabled }` | 2026-10-26,scope `write:inbound`。让处理规则生效(`active:false` 关闭,消息只通知负责人)。Project owner / lead,只限本人 token |
| `pause_project_inbound_connection` / `resume_project_inbound_connection` | `project_id`, `source_id`(uuid), `idempotency_key`, `dry_run?` | 同上(`state: paused\|active`) | 2026-10-26,scope `write:inbound`。暂停期间消息被拒收,不删除任何东西;与网页/App 暂停、恢复同一命令。只限本人 token;已断开的回 `connection_disconnected` |
| `disconnect_project_inbound_connection` | `project_id`, `source_id`(uuid), `idempotency_key`, `dry_run?` | 同上(`state: disconnected`,`outcome: changed\|already`) | 2026-10-26,scope `write:inbound`。断开连接:凭据立即失效,之后的消息一律拒收,历史保留(已断开)。我的空间只有本人能断开。只限本人 token |
| `check_project_connection_operation` | `project_id`, `operation`(上面五个之一), `idempotency_key` | `{ state:"not_found"\|"in_progress"\|"completed", result? }` | 2026-10-26,scope `read:projects`。回答丢失后按同一 key 读结果,从不执行;`not_found` = 没有执行(可用同一 key 重发);`in_progress` 带 `started_at`:暂停/恢复/规则/断开超过 2 分钟仍未完成时,用同一 key 重发会接管重跑,创建不会;只读本 token 自己的操作,保留 24 小时;结果从不含凭据。只限本人 token |
| `list_inbound_connection_request_reminders` | `view?`(`pending`\|`initiated`\|`handled`,默认 `pending`) | `{ view, complete, items[{ item_id, request_id, project{id,name}, name, status, summary, requester_user_id, waiting_since, handled_by_you_at, href }] }` | 2026-10-22 (B1.136),scope `read:projects`。与网页「决策」同一读模型,跨你所在的全部 Project:`pending` = 你当前可批准的申请(仍是该 Project 管理者),`initiated` = 你发起且仍在等待的申请,`handled` = 已批准/拒绝/关闭且由你发起或在你管理的 Project 中。`item_id` 为 `source_request:<request_id>`(原申请,不另建审批对象)。只读:列出/阅读不批准、不减少待处理数;失去成员或管理者身份后立即不再列出。批准在 `href`(Project › 设置 › 集成)或 App 完成,拒绝用 `reject_project_inbound_connection_request` |
| `invite_user` | `email`, `name`, `role?`(默认 `member`,枚举 `member`/`admin`/`hr_admin`/`super_admin`/`assistant`), `location?`(默认 `overseas`), `ui_language?`(默认 `zh`), `status?`(默认 `active`) | `{ user_id, email, name, status, invite_sent: true }` | Admin-only(v0.24.11,scope `admin:users`)。包装 `createUserAdmin` 表单 action 抽出来的 `createUserCore`(`src/app/(internal-os)/users/actions.ts`):新建 user row + onboarding 清单 + 通知偏好 + 欢迎 inbox push。`domain_not_allowed` / `user_already_exists` 走 problem+json(见 § 8.4)。Charter Art 9:audit 记 caller 为 actor。2026-10-05 起不再接受 `employment_type`:产品不记录用工形式;旧客户端仍传该参数会被忽略(不报错、不写入任何值) |
| `search_user_by_username` | `username`(email 局部,即 `@` 前那段;不带 `@`), `only_active?`(默认 `true`) | `{ items: [{ user_id, email, name, role, status, domain, created_at }] }` | Admin-only(v0.24.12,scope `admin:users`)。按 `email ILIKE '<username>@%'` 服务端查全部允许域名下匹配的 user 行,返回 0+ 条,每条带 `domain`(`fluxprimestudio.com` / `zohomail.com` …),caller 自己挑 canonical 邮箱。v0.24.x 放开 COMPANY_EMAIL_DOMAINS 后 fd1 CLI 不能再硬编码后缀,需要这条来 disambiguate。空结果 = `{items: []}`(不是 422,合法 outcome)。`username` 带 `@` → 422 `username_contains_at`(让 caller 改成只传 local-part)。Charter Art 7:admin 本来就有 user list 读权,回显 email/name/role OK;不返 password hash / token |
| `set_my_avatar` | `image_base64`(JPEG/PNG/WebP 原图字节,≤ 10 MB、≤ 4000 万像素;不接受 URL), `crop?: {x, y, size}`(方向校正后的原图像素正方形) | `{ contract, avatar, default_avatar, has_upload }` | **只改调用 token 自己账户**的头像(无目标账户参数);服务端隔离解码、去除全部元数据、失败保留旧头像;拒绝 Agent 身份 token;Project 绑定 token 不可见。scope `write:profile` |
| `clear_my_avatar` | — | 同上 | 恢复默认:撤下自己上传的头像,回到稳定的默认图案(不重新生成);幂等。scope `write:profile` |

里程碑读取是 `content.read`；个人连接只有查看权限时只列出 list/get。创建、编辑和关联使用 `milestone.*` 的 manage 级，与本人 Web 权限、项目 AI 上限和原项目范围取交集；`write:tasks` / 旧 `write:projects` 任一即可。归档为 `object.delete`，AI 始终只得到 `proposal_only`，由本人在项目「里程碑」完成。所有写入保留预演、幂等与操作记录。

**Project 助手:** `add_task_comment` / `update_task_status` / `add_task_cc` / `remove_task_cc` / `set_workitem_delivery_target` 不对 Project 助手开放(需要时用 `ask_human`;运行进度用 `update_task_run_progress` / `dispatch_report`)。调用会得到 `forbidden`,不改变任何数据(v1.37)。

每个写操作都进 `audit_logs` 表(`source: "mcp"`)。token owner 在 /me/agent 看得到自己的调用历史摘要;admin 在 /settings/audit-logs 可审计全公司。

---

### 4.2 附件（Attachments，unified-MCP-attachments，2026-07-24）

一套跨 UI 与 MCP 的附件能力，按语义用途绑定，避免 25–50 MB base64 塞进 JSON-RPC。

**分段上传三步**（bearer token，scope：`write:tasks:draft` 或 `write:feedback:draft`）：

1. `prepare_attachment_upload` — 入参 `filename` / `content_type` / `size_bytes` / `sha256` / `idempotency_key?`；校验类型与体积上限后返回 `{ upload_id, upload_url, expires_at, max_bytes, allowed_content_types }`。
2. `POST {upload_url}`（即 `/api/internal-os/mcp/attachments/upload`，multipart 字段 `file` + `upload_id` + `sha256`，同一 bearer token）：服务端复用规范签名校验，声明 sha256 与实际字节不符即 fail-closed；幂等——同 `upload_id`+同字节复用同一 ref，同 id 异字节返回 403。返回 `{ attachment_ref, ... }`。
3. `finalize_attachment_upload` — 入参 `upload_id`（可带 `sha256` / `size_bytes` 复核）；确认后返回 `{ attachment_ref, filename, content_type, size_bytes, checksum_sha256 }`。`attachment_ref` 是上传者私有的 `inline_draft`，24h 未绑定由 cron 回收。

**把 ref 绑到写操作**（可选字段，均 ≤20 个 uuid，非本人上传/已绑定的 ref 静默忽略）：

| 工具 | 新增字段 | 语义用途 |
|---|---|---|
| `create_task` | `acceptance_criteria?`、`description_attachment_refs?[]`、`acceptance_attachment_refs?[]` | 描述附件 / 完成标准附件 |
| `add_task_comment` | `attachment_refs?[]` | 工作项讨论评论 |
| `submit_delivery_evidence` | `evidence_attachment_refs?[]`（与 `evidence_url?` 并存，向后兼容；不会自动通过/采用） | 交付依据 |
| `create_feedback` | `attachment_refs?[]` | 反馈正文 |
| `add_feedback_comment` | `attachment_refs?[]`（并入 `client_message_id` 幂等指纹：同 body 不同附件集 → 冲突） | 反馈回复 |

**读**：`get_task` / `get_feedback` 返回按用途分组的安全描述符（`{ id, filename, content_type, size_bytes, purpose, source_tool }`，从不含存储 key），并挂在对应评论上；隐藏评论不暴露附件。

**取字节**：`GET /api/internal-os/mcp/attachments/{id}`（bearer token）复用与浏览器同一套可见性授权，并按文件来源检查 scope：聊天 `read:chatter`、反馈 `read:feedback`、工作项 `read:tasks`、文档 `read:docs`、会议 `read:projects`；其他来源（含未绑定的内联草稿）一律拒绝。同时执行与读取其上级对象的工具相同的 AI 闸门（共享项目 AI 需带 `?execution_id=`），绑定到项目的凭证读不到私聊文件。语音片段对任何凭证都拒绝：语音只能在产品里播放。非授权返回不泄露的 404；每次读取写一条 `mcp_call_logs`（`mcp_attachment_download`）；不返回永久公开 URL。

### 4.1 WorkItem 统一 schema 与 kind 字段冻结(v0.26.531 · ADR-0008)

系统本体只有一个 **WorkItem**。历史工具 payload 里出现的 `kind`(feature/issue/chore)、`task_id`、`requirement_id` 等字段以及 `REQ-`/`DISP-` code 前缀 **全部兼容冻结(deprecated-compat)**:字段继续原样返回、不会消失、含义不变,但它们反映的是旧的内部存储分类学,未来主版本会移除。

**约束(请现在就遵守):**

- **不要**在 agent 逻辑里对 `kind` / requirement-vs-task 分类做行为分支——分类不改变任何后续行为,分支只会带来认知税。
- 新集成请读取 `get_task` 返回中的 `task.work_item` 对象,或直接用 `get_work_item`:它暴露的是属性词汇(状态机对、`lifecycle` 投影、`source`、`parent_id`/`depth`、`executable`),这是长期稳定的协议面。
- 现有工具的入参与出参形状保持冻结,已经写好的客户端无需改动。

### 4.2 聊天未读提及锚点（B2.2.1）

`list_chatter_messages`（`read:chatter`）在原消息页旁增加 `mention_message_id: string | null`：当前调用人在该群聊中能读到、仍未读且提到自己的最早消息 ID；无则为 `null`。筛选与会话目录的公开消息未读提及计数相同，排除别人的私聊、删除/过期内容、本人发送以及已读消息。锚点可能在本次分页窗口以外；它是定位引用，不扩大读取权限。原 `channel_id`、`since`、`order` 与 `limit` 参数不变；读取与取得锚点均不标为已读。

## 5. 典型用法

### 5.1 早上 standup

> 「Claude,帮我看看 inbox 有哪些昨晚我不在的时候发生的新东西」

Claude 调 `list_inbox({ unreadOnly: true })` → 给你摘要,如果有重要的会建议你点开看 / 标已读。

### 5.2 提交今日同步

> 「我今天专注处理 REQ-3421 的登录回归。设我状态为 focused」

Claude 先调 `list_tasks({ assignee: "me" })` 确认真实 WorkItem，再调 `submit_daily_update({ availability_status: "focused", note: "专注处理 REQ-3421 登录回归" })`。Daily Sync 只记录协调信号；任务负责人、进度和完成状态仍只从 WorkItem 读取和修改。

### 5.3 标记完成 vs 关闭(不做了)

> 「REQ-1234 我刚部署上线了,标记完成」

Claude 调 `update_task_status({ taskId: "...", status: "done", note: "已部署到 prod" })`。

> 「REQ-1240 不做了,需求撤回」

Claude 调 `close_work_item({ project_id: "...", work_item_id: "...", reason: "需求撤回,不再推进" })`。关闭不是完成:结果是 `outcome: "closed"`,不计入任何完成统计;执行者会收到带原因的通知。

### 5.4 批量看反馈

> 「最近 3 天反馈池里跟 'standup' 相关的都有谁提的」

Claude 调 list_inbox / list_tasks 反复查 + 给你聚合摘要。

### 5.5 给别人创建需求前读取接单偏好

> 「帮我给 Leslie 建一个官网 UI 改版需求，尽量按她接单习惯把背景问清楚」

Claude 先调 `get_owner_intake_templates({ owner_email: "leslie@..." })`，读取系统固定标题 / 描述起稿，以及 Leslie 维护的需求模板；如果 Leslie 没维护模板，返回 admin 设置的系统默认模板。然后 Claude 用 `description.label` / `description.body` 做起草骨架，把 `checklists` 中合适的模板融入真实描述，你确认后再调 `create_task({ assignee_email: "leslie@...", ... })`。

这条路径只是在创建前补齐背景，不会把清单变成强制字段，也不会绕过项目 / 任务权限。

---

## 6. 限频、安全、可见性

- **Quota**：通用 60 次 / 分钟、5000 次 / 天目前是 advisory accounting，不是已验证的拒绝闸门。正式数值 TBD by lane 04，enforced from lane 04；届时发布组织 / key / IP 预算、并发、429 和重试契约。不能据这些旧数字承诺容量。
- **外部观察**：普通外部 key 目前可轮询 eligible inbox 工具，须退避并重读源状态。通用 Project 订阅、事件 replay 和外部 webhook 管理尚未提供；SSE 或 tools.listChanged 不等于耐久业务事件。事件由 lane 06 交付。
- **Auth scope**:token = 单个 user。Agent 只能查看到 / 改这个 user 自己能访问的数据。**没有跨用户 token**。
- **可见性守底**(Charter Art 5):agent 调 `list_tasks` 也只能查看到你参与的项目里的需求;别的项目即使存在也读不到。同样适用 list_inbox / list_projects。
- **写操作 audit**:所有写都进 `audit_logs`,`source` 字段标 `mcp`,`metadata` 里带 tool 名 + 输入摘要。一旦 token 泄漏,你或 admin 可以从 audit 找到所有可疑写操作。
- **Token 轮换**:发现泄漏 → 在「我的 Agent」中断开该连接 → 重新连接并更换客户端凭据。被撤销的 token 立即 401(无 grace window,跟 webhook 那边的 24h overlap 不同 —— MCP 是 agent 主动调用,轮换造成的"agent 偶尔失败"是正常重试范畴)。

---

## 7. Token 权限范围 (Scopes)

> v0.17.71 起:每个 token 携带一个 `scopes: string[]` 字段;tool 调用前会校验 token scope 是否覆盖 tool 声明的 `requiredScopes`。
>
> v1.37(MCPSCOPE-1025)起:每个工具声明的 `requiredScopes` 在工具运行前强制执行,对每一种 token 都一样(个人 token、Project 助手、接入的 Agent、`*`)。缺少 scope → `error_code` `forbidden`、`error.status` 403、`required_scopes`,`workflow_status.next_action` 说明怎样拿到(个人 token:/me/tokens;接入的 Agent:在 我的 → Agent 与授权 → 我的 Agent 重新连接并选可写入的权限;Project 助手:请 Project 管理员调整)。`fluxdesk_help`、`whoami` 和 `ping` 对每一种 token 都可用。`mcp_smoke_test` 准确报告该 token 能调用哪些工具。

### 7.1 向后兼容(已有 token 不受影响)

老 token + 默认场景 = 全部权限。具体:

- 0.17.70 及以前签发的 token 在 0104 migration 后自动获得 `scopes = ['*']`(wildcard,等价旧行为)。
- 新接入以「接入我的 Agent」卡片的层级和服务端映射为准；不要用旧 token 页的 `*` 默认值推断新 grant。
- 没有任何 in-place 升级 / 重新生成需求;直接 deploy 就完事。

### 7.2 Scope 词表

| Scope key | 覆盖的工具 |
|---|---|
| `*` | **全部**(默认,向后兼容)|
| `read:*` | 所有 `read:` 类工具 |
| `read:tasks` | `list_tasks`、`get_task`、`get_owner_intake_templates`、`get_project_work_review`、`get_focus_progress` |
| `read:feedback` | `list_feedbacks`、`get_feedback`、`search_feedback`(绑定项目的 token 见表后「反馈的范围」) |
| `read:inbox` | `list_inbox`、`get_notification` |
| `read:audit` | 读自己 / admin 读全公司 audit;`list_project_audit_log`(项目成员读该项目操作记录) |
| `read:decisions` | `get_project_decision_review`(与 App 决策详情同一已授权投影,只读) |
| `read:daily` | `get_my_work_summary`(本人工作汇总;只限本人个人 Token) |
| `write:daily` | `generate_my_work_summary`(显式生成本人工作汇总;只限本人个人 Token) |
| `read:meta` | `list_projects`、`whoami`、组织元数据、`get_avatars`、`list_my_agent_connections`、`get_my_agent_connection_activity`(v1.37) |
| `read:projects` | `list_project_repositories`、`list_project_repository_commits`、`get_meeting`、`list_project_connected_systems`、`get_project_connected_system`、`get_project_modules`、`check_project_module_operation`、`list_project_templates`、`get_project_template`、`preview_project_template_switch`、`check_project_template_operation`、`get_project_assistants`、`milestone_list`、`milestone_get`(v1.39)、`project_rules_list`、`project_rules_get`、`project_rules_runs`、`project_rules_check_operation`、`project_rules_dry_run` |
| `read:docs` | `list_docs` / `get_doc` / `search_docs` / `list_tags` / `search_by_tags` / `list_doc_drafts` |
| `write:tasks` | Task 写父 scope：兼容覆盖 `write:tasks:create` / `write:tasks:draft` / `write:tasks:publish`，以及 `claim_task`、`update_task_status`、`add_task_comment` 等后续修改;`update_focus_progress` |
| `write:tasks:create` | 仅 `create_task(as_draft=false)`；不能 claim/update/publish draft、修改 Evidence/Decision、writeback 或 release |
| `write:feedback` | `add_feedback_comment` 等 |
| `triage:feedback` | `close_feedback`、`add_feedback_cc`、移除他人的 `remove_feedback_cc`、平台反馈的 `dispose_feedback`;`write:feedback` 与 `write:*` 都不包含它,只有 `*` 包含;不在默认、Project 助手或 OAuth 连接范围内(SEC-1027) |
| `write:meetings` | `create_meeting`、`update_meeting`、`get_meeting`、`prepare_meeting_link`、`get_meeting_link`、`revoke_meeting_link`、`expire_meeting_link`、`convert_meeting_link`、`get_meeting_management`、`command_meeting_management`(会议外部访客与资料;Project token 只能用于其本项目) |
| `write:docs` | 文档写父 scope。兼容覆盖 `write:docs:draft` + `write:docs:publish` |
| `write:docs:draft` | `create_doc_draft` / `update_doc_draft` / `tag_doc` / `untag_doc`(draft docs) |
| `write:docs:publish` | `update_doc` / `publish_doc_draft` / `archive_doc` / `tag_doc` / `untag_doc`(published docs) |
| `write:inbox` | `mark_notification_read`、`mark_all_notifications_read` |
| `write:profile` | `set_my_display_name`（本人改名）、`set_my_avatar`、`clear_my_avatar`、`stop_my_ai_connection`(v1.34)、`revoke_my_agent_connection`(v1.37):只作用于 token 自己的账户(2026-10-14,ADR 107)。不在通用勾选列表与 Project Agent 上限内;`*` / `write:*` 个人 token 已覆盖 |
| `write:decisions` | `append_decision_outcome`、`append_decision_review`、`set_decision_fields`:给决策链追加事实，从不做决定(2026-10-24,ADR 135)。不在通用勾选列表与 Project Agent 上限内;`*` / `write:*` 个人 token 已覆盖 |
| `write:projects` | `set_project_module_enabled`、`reorder_project_modules`、`rename_project_module`、`switch_project_template`、`save_project_as_template`、`manage_saved_project_template`(v0.26.2668)、`set_project_assistant_service`、`set_project_assistant_member_access`（兼容旧里程碑调用；新的个人连接用 `write:tasks`，不因此获得项目设置权限）:经 Project 设置权限修改 Project 设置(功能与页面:模块装卸、顺序、显示名;模板更换与保存);需活跃的管理员/协调员;不在 Project Agent 上限与通用勾选列表内,`*` / `write:*` 个人 Token 满足(v1.34)、`project_rules_create`、`project_rules_update`、`project_rules_pause`、`project_rules_delete` |
| `write:inbound` | 入站事件投递 |
| `write:inbound:bulk` | 入站事件**批量**投递 |
| `admin:catalog` | catalog row / bundle CRUD(create / update / delete / list / get + 6 bundle 工具)+ `list_inbound_tokens` 发现(给 `broadcast_event` 拿 `tools_token_id` UUID,admin only) |
| `admin:approvals` | 审批类工具:`assign_catalog_to_employee` / `approve_team_support_request` / `revoke_catalog_approval` / `list_pending_team_support_requests` / `list_approvals_for_user`(v0.21.0 起从 `admin:catalog` 拆出;team-lead approver token 用这个就够,不需要给 catalog 写权限) |
| `admin:tokens` | 集成接入 Token 的 mint / revoke:`issue_admin_tools_claim_token` / `issue_admin_moderation_token` / `revoke_admin_token`(v0.22.2 起从 `admin:catalog` 拆出。整合 CLI 拿这一条就够 mint 自家 `flux-tools-…` 接入 token + 应急吊销,不需要 catalog CRUD 权限) |
| `admin:broadcast` | `broadcast_event` 系统广播(v0.21.0 起从 `write:inbound:bulk` 迁过来:广播本质上是 admin 操作,不是普通 bulk ingest) |
| `admin:on_behalf_of` | 代用户提交 /team-support 申请(admin only,**比 `admin:catalog` 更窄**:只能 file pending,不能 approve / revoke) |
| `admin:projects` | **Deprecated 但仍接受**：原先的项目管理类工具 scope。`bind_project_repository` / `unbind_project_repository` 已迁到 `code:repo:bind`（见下条），但持有 `admin:projects` 的旧 token 仍可调用,不需 re-mint |
| `code:repo:bind` | 绑定 / 解绑项目代码库:`bind_project_repository` / `unbind_project_repository`(v0.25.x 新增,kingston 855a8a93 follow-up)。**注意**:token scope 只决定 token 能不能 *调* 这两个工具;真正的「这个用户能不能绑这个项目」由服务端的 `userCanManageRepoBinding(user, project_id)` 在调用时实判 —— 必须是 platform super_admin、本项目 Project Lead、本项目 Tech Lead,或持有 `code.repo.bind` 能力(参 `src/lib/internal-os/repo-binding-permission.ts`) |
| `admin:users` | 创建新成员:`invite_user`(v0.24.11 起新增。fd1 UX#15 #35 P1 — 之前没有 MCP 路径建账号,整合 CLI 必须打开浏览器走 /users 表单。Kept narrow — 不解锁 catalog CRUD 或 token mint;granting alone 就能在 CLI 一键拉新用户)|
| `admin:*` | 所有 admin 工具(含 catalog / approvals / tokens / broadcast / on_behalf_of / projects / users)|

**反馈的范围(2026-10-25,mcpfb-1025):** 绑定到一个项目的 token(「接入我的 Agent」、项目助手)用 `list_feedbacks` / `get_feedback` / `search_feedback` / `add_feedback_comment` / `update_feedback_comment` 时，只够得到**本项目的反馈**和**这个人自己提交的平台反馈**,不再继承这个人能看到的全部项目反馈(`search_feedback` 只搜本项目;自己的平台反馈经 `list_feedbacks` / `get_feedback` 可达)。`dispose_feedback` 更严：只能处理本项目的反馈，不能处理任何平台反馈。别的反馈不列出;按 id 读、回复或改评论时，答复与「不存在」完全相同(`feedback not found` / `not_found`),不泄露是否存在。`whoami.binding.projectId` 是这个项目，`fluxdesk_help` 的 `you.feedback` 用一句话说明范围。跨项目处理平台反馈(产品负责人的运维助手)是**单独授予**的:`platform_feedback_triage_grants` 里有该项目助手的有效记录，且 token 主人当下是 super_admin,`whoami.baselineCapabilities` 显示 `platform_feedback:triage`;没有自助或项目管理员入口。规则只在一处:`src/mcp/lib/feedback-credential-scope.ts`。不绑定项目的个人 token 不变。

**平台反馈只到自己的(2026-10-30,fbmcpread-1030):** 不绑定项目的个人连接(你自己的 Agent)用 `list_feedbacks(platform_only=true)` / `get_feedback` / `search_feedback` / `add_feedback_comment` 时，只够得到**这个人自己提交的平台反馈**,和网页一致：别人提交的平台反馈不列出、不计入 `totalCount`;按 id 读、回复、改或隐藏评论、`dispose_feedback` 时，答复与「不存在」完全相同。只有平台超级管理员本人的连接能看到并回复全部平台反馈(平台反馈监控用的就是这种)，和网页一致。「也可以处理平台反馈」(`triage:feedback`)只让处理状态和标「已上线」的命令能执行，不扩大能看到的范围。抄送(关注)只决定谁收到通知，不让人看到别人的平台反馈。`whoami.baselineCapabilities` 相应显示 `platform_feedback:read_own` / `platform_feedback:comment_own`(只到自己的)或 `platform_feedback:read` / `platform_feedback:comment`(全部)。项目反馈的规则不变。

**我的反馈(2026-10-26,quietfix-1026):** `list_feedbacks` 加 `mine: true` 时，列出的是 Web / App「我的反馈」同一份列表：这个人自己提交的平台反馈。默认只列进行中的(`needs_info` / `new` / `triaged`);`scope: "all"` 才包括已采纳和已关闭的历史。`mine` 不能和 `project_slug` / `status` / `author_email` / `since` 同时用;`scope` 只能和 `mine: true` 一起用。返回 `{ totalCount, items: [{ id, title, status, summary, project: null, createdAt, updatedAt, latestActivityAt, replyCount, unreadReplyCount, url }], nextCursor }`;游标只在同一个 `scope` 下有效。读这份列表不会标记已读、不改任何状态或计数。绑定项目的 token 也可用(自己的平台反馈本来就可达)。

**v0.21.0 back-compat 注意**: `admin:catalog` token **仍然**满足要求 `admin:approvals` 的工具(`hasScope` 内置 one-way superset),旧 token 不会 silently 401。反向不通(`admin:approvals` 不解锁 catalog 写),这是拆分的目的。

**v0.22.2 back-compat 注意**: 同样的 one-way superset 也覆盖 `admin:catalog` ↦ `admin:tokens`。已签发的 `admin:catalog` token 可以直接调 5 个 token mint/revoke 工具;新的 `admin:tokens` 窄 token 不能反向 unlock catalog CRUD。

**v0.24.11 back-compat 注意**: 同样的 one-way superset 也覆盖 `admin:catalog` ↦ `admin:users`。已签发的 `admin:catalog` token 可以直接调 `invite_user`,无需 re-mint;新的 `admin:users` 窄 token 不能反向 unlock catalog CRUD。

### 7.3 通配符规则

- `*` 匹配**所有** scope。
- `<module>:*`(例:`read:*`、`write:*`、`admin:*`)匹配该模块下所有 scope。注意 **module 之间不互通** —— `read:*` 不会授权 `write:tasks`,反之亦然。
- 否则按字面值精确匹配。
- 只有 `fluxdesk_help`、`whoami`、`ping` 不需要 scope,在任意 token 下都可调用,这是为了让用户随时能验证 token 还在工作 + 检查身份。其他工具若没有声明 `requiredScopes`,对每一种 token 都拒绝(fail closed,v1.37)。

### 7.4 怎么挑

| 场景 | 推荐 scope |
|---|---|
| 个人 Claude Code / Desktop 日常使用 | `*`(简单省事) |
| CI / 只读巡检脚本 | `read:*` |
| 跨系统投递入站事件的 receiver | `write:inbound`(批量场景再加 `write:inbound:bulk`) |
| 经单独授权、只需自动创建 WorkItem 的 managed integration | `read:tasks` + operator-managed `write:tasks:create`(不授予后续修改或发布草稿) |
| 需要完整 Task 写能力的受信 agent | `read:*` + `write:tasks`(明确不给 admin) |
| Admin 自己的 token | `*` 或 `admin:*` + `read:*` |

### 7.5 缩窄 scope 后 token 还能用吗?

能。任何用 `*` 签发的 token 跟之前完全一致。如果你**主动**生成了一个 `read:*` 的 token,然后让 agent 去 `create_task`,server 会回 tool 错误 `error_code: "forbidden"`(`error.status` 403,`required_scopes` 列出缺的 scope,`workflow_status.next_action` 说明怎样拿到),工具不会运行,也不改变任何数据。普通受信客户端确实需要 claim/update/comment 等完整写能力时使用父 scope `write:tasks`；单独获准的 managed integration 可由 operator 配置窄 scope `write:tasks:create`，它不出现在通用 token picker/default Project Agent scope 中。

### 7.6 客户端怎么知道自己有哪些 scope?

- `whoami` 返回里包含 `scopes` 字段。
- `tools/list` 返回的每个 tool metadata 里带 `requiredScopes`,客户端可以预测哪些工具会被拒。
- `mcp_smoke_test` 的 `canCall` 与调用时的 scope 判断一致;`fluxdesk_help` 只列出本凭据现在能调用的工具。

---

## 8. 错误码

| HTTP / JSON-RPC code | 含义 | 应对 |
|---|---|---|
| `401 UNAUTHORIZED` | token 缺 / 错 / 已吊销 / 已过期 | 重新生成 |
| `403 FORBIDDEN` | tool 调用涉及别人的数据 / 你没权限的 project | 不重试,改输入 |
| `404 NOT_FOUND` | id 不存在 或 不在你的可见范围里 | 不重试 |
| `429 TOO_MANY_REQUESTS` | 限频 | 按 `Retry-After` 重试 |
| `400 INVALID_INPUT` | 输入 schema 错(例如 `priority` 不是 p0/p1/p2) | 不重试,修客户端 |
| `500 INTERNAL_ERROR` | FluxDesk 内部 bug | 重试一次;持续报告给 admin |

JSON-RPC 错误体:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32602,
    "message": "INVALID_INPUT: priority must be p0/p1/p2",
    "data": { "field": "priority" }
  }
}
```

### 8.1 Tool-result 错误信封(v0.19.5)

MCP 把 tool 的执行结果统一包在 `result.content[0].text` 里(JSON-RPC 层面不区分成功 / 失败)。**信封里**的 JSON 形态目前有两种,客户端两种都要会解:

**flat-string(默认,绝大多数写 tool):**

```json
{ "error": "feedback not found" }
```

**结构化 RFC 7807-shaped(v0.19.5 起,目前 `add_feedback_comment` + `create_feedback`(v0.19.9)):**

```json
{
  "error": {
    "type": "validation_failed",
    "title": "Body too long",
    "detail": "body must be ≤ 8000 chars",
    "field": "body"
  }
}
```

`type` ∈ `{ validation_failed | not_found | forbidden | conflict | internal }`。`field` 可选,指向出问题的输入字段。后续会逐 tool 迁移过去 —— 客户端先按 `typeof result.error === "string"` 做兜底分流即可。

### 8.2 `broadcast_event` 的 `error_envelope.next_action`(v0.24.8 起)

`broadcast_event` 每条 per-recipient 失败 + 顶层失败的 `error_envelope` 现在多带一个 `next_action` 字段 —— 一句话告诉 sender「下一步该做什么」,避免 caller 拿到 `code` 后还要回头查文档:

```json
{
  "delivered": [
    {
      "email": "alice@x.com",
      "error": "no_approved_request",
      "error_envelope": {
        "code": "no_approved_request",
        "title": "No approved team-support request",
        "detail": "recipient has no approved team-support request for the supplied tools_token_id; auto-claim requires opt-in via the /team-support catalog first",
        "field": "recipients",
        "next_action": "recipient must approve the linked catalog row (use `list_inbound_tokens` to see `linked_catalog_slug`) via /team-support → 「我的申请」 first; or call `assign_catalog_to_employee` to file + auto-approve on their behalf"
      }
    }
  ]
}
```

**code → next_action 映射**(完整列表;optional,某些 code 没有有意义的下一步就不带这个字段):

| code | next_action 大致方向 |
|---|---|
| `tools_token_not_owned` | mint your own admin tools-claim token via `issue_admin_tools_claim_token` MCP tool, then pass its UUID |
| `no_approved_request` | recipient approves the linked catalog via `/team-support`,或 admin 用 `assign_catalog_to_employee` 代办 |
| `no_token` | pass `tools_token_id` + 默认 `auto_claim:true` 内联 mint |
| `user_not_found` / `user_inactive` | drop offending email / reactivate via `/users` |
| `recipient_hint_forbidden` / `_required` | 删除 / 添加 `recipient` 字段 |
| `cross_project_blocked` / `project_not_found` / `project_archived` | 删除 `project_slug` 或换活跃项目 |
| `token_disabled` | recipient 自己 / admin 重新启用 token |
| `circuit_open` | 等 breaker 自动恢复或 admin 手动 reset |
| `rate_limit_global` | 等 daily 计数 00:00 UTC 重置或 admin 调高 `daily_limit` |
| `forbidden_admin_only` | 换 admin 调用,或者改用 `/api/v1/events` 单发 |
| `schema_invalid` | 修对应 `field` 后重试 |

**Charter Art 7**:`next_action` 永远只指动作路径(MCP tool 名、UI 路径、运营动作),**绝不**泄露 token 值、邮箱列表、其它用户身份等敏感信息。整合 CLI 把这个 hint 原样透传给操作员即可,无需脱敏。

### 8.3 admin write 工具 `user_not_found` 统一 problem+json(v1.9 起)

之前几个 admin write 工具(`assign_catalog_to_employee` / `create_team_support_request_on_behalf` / `revoke_catalog_approval` 等)在收到「邮箱查不到 user」时返回 flat-string `{"error":"user not found"}`,跟 v0.19.5 起其它 tool 的 problem+json 风格不一致。

v1.9 起,**所有** admin write tool(走 `findUserByEmail` 邮箱寻址那批)在 user-not-found 时统一返回:

```json
{
  "error": {
    "type": "https://docs.fluxdesk.net/api/v1/errors/user-not-found",
    "title": "User not found",
    "status": 422,
    "detail": "No user matched email 'alice@x.com'",
    "email": "alice@x.com"
  }
}
```

对应 REST endpoint(`POST` / `DELETE /api/v1/catalog/approvals`)直接返回 422 problem+json body(去掉外层 `{"error":...}` 包装,因为 REST 用 `Content-Type: application/problem+json` 直接吐结构化体):

```json
{
  "type": "https://docs.fluxdesk.net/api/v1/errors/user-not-found",
  "title": "User not found",
  "status": 422,
  "detail": "No user matched email 'alice@x.com'",
  "email": "alice@x.com"
}
```

源码常量在 `src/lib/api/problem-json.ts` → `ProblemTypes.UserNotFound`,builder 是 `userNotFoundProblem(email)`(纯数据形)+ `userNotFound(email, opts)`(直接返 `NextResponse`)。整合 CLI 应该 route on `type` URI(stable),不要 fuzzy-match `detail`。

覆盖范围:`assign_catalog_to_employee`、`create_team_support_request_on_behalf`、`revoke_catalog_approval`(仅 `catalog_slug + email` 寻址路径;`approval_id` 路径 user_not_found 含义是数据完整性问题、没有有意义的 caller-supplied email,保留 flat-string)。`POST /api/v1/catalog/approvals`(assign)+ `DELETE /api/v1/catalog/approvals`(revoke)同样切到 problem+json。

**Charter Art 7**:这些都是 admin 工具(token scope `admin:approvals` / `admin:catalog` / `admin:on_behalf_of`),admin 本来就有 user list 访问权,所以回显 email 是 OK 的;非 admin 场景(`inbound/personal` / `/api/v1/events` 的匿名探测路径)依然走 401 兜底、不会回显邮箱(见 `checkTargetMemberApproval` 的 gating 规则)。

### 8.4 `invite_user` 错误类型(v1.11 起)

`invite_user`(scope `admin:users`)有两个独立的 problem+json 错误类型,跟 § 8.3 同走 RFC 7807 信封:

**域不在白名单**(`COMPANY_EMAIL_DOMAINS` env 控制):

```json
{
  "error": {
    "type": "https://docs.fluxdesk.net/api/v1/errors/domain-not-allowed",
    "title": "Email domain not allowed",
    "status": 422,
    "detail": "Email 'alice@randomdomain.com' is not in the configured company-domain allowlist",
    "email": "alice@randomdomain.com",
    "domain": "randomdomain.com"
  }
}
```

**邮箱已存在用户**(idempotency miss):

```json
{
  "error": {
    "type": "https://docs.fluxdesk.net/api/v1/errors/user-already-exists",
    "title": "User already exists",
    "status": 422,
    "detail": "A user with email 'alice@company.com' already exists",
    "email": "alice@company.com",
    "user_id": "abc12345-..."
  }
}
```

CLI 应该 route on `type` URI(stable),不要 fuzzy-match `detail`。`user_already_exists` 体里附带的 `user_id` 让 CLI 可以选择把它当成 idempotency hit(直接用已有 row,不报错)还是硬失败。源码:`src/lib/api/problem-json.ts` → `ProblemTypes.DomainNotAllowed` / `ProblemTypes.UserAlreadyExists`,builder `domainNotAllowedProblem(email)` / `userAlreadyExistsProblem(email, userId)`。

**Charter Art 7 守底**:`invite_user` 是 admin-only(scope `admin:users` + `isAdminRole` 二次校验),回显 email + `user_id` OK;非 admin 路径不会触达这两个错误类型(scope 校验先于业务逻辑)。

---

## 9. 故障排查

**`401 token_not_found`** — token 拼错 / 已撤销 / 没复制全。重新生成,Authorization header 是 `Bearer <token>`(注意 Bearer 后有空格)。

**`Connection refused` / 超时** — 网络问题。先 `curl https://my.fluxdesk.net/api/internal-os/healthz` 看 FluxDesk 是否可达,再排查公司 DNS / VPN。

**Claude / Cursor 不显示 tools** — 多半是 config 文件路径错或者 JSON 语法错。看客户端的 MCP 日志(Claude Code: `~/Library/Logs/Claude/`,Mac;Cursor: 设置 → MCP → 「View Logs」)。

**调用返回 `403 cross_project_blocked`** — 你想读的 task / project 不在你参与的范围里。这是 Charter Art 5 守底,不是 bug。让对应项目的 admin 把你加进去就能读。

**写操作"成功"但页面没变化** — 大概率是 fetch / SW 缓存。F5 硬刷新一次。如果还不行,看 `/me/agent` 下的 audit 摘要,确认 server 确实收到调用。

---

## 10. 反模式

| ❌ | ✓ |
|---|---|
| 把 token 写进 git commit / 公开仓库 | 用客户端的 secret manager(macOS Keychain / Windows Credential Manager / 1Password) |
| 跟同事共用 token | 每人自己在「接入我的 Agent」中连接;token 1:1 强绑 |
| 用 cron 每 5s 调 list_inbox 轮询 | 按需轮询已获准的收件箱工具并退避；普通外部 key 的通用事件订阅与重放尚未提供 |
| Agent 自动每天提交固定的 standup | standup 是结构化协作信号,要人参与决定,不该机器代填 |
| 写脚本批量 create_task 灌历史数据 | 历史数据走 admin 后台 / migration,不走 user MCP |

---

## 11. 服务端实施参考(给后续维护)

| 实施点 | 文件 |
|---|---|
| Express handler | `src/mcp/server.ts` (`/mcp` endpoint) |
| Auth | `src/mcp/auth.ts`(读 `accessCredentials.aiTokenPrefix` + bcrypt) |
| Tools 注册 | `src/mcp/tools/index.ts` + `tools/*.ts`(inbox / tasks / projects / daily / writes) |
| Audit | `src/mcp/lib/audit.ts`(每个写 tool 写 `audit_logs` `source=mcp`) |
| Token CRUD | `src/app/(internal-os)/me/agent/actions.ts`(generate / revoke) |
| Token Manager UI | `src/components/internal-os/mcp-token-manager.tsx` |
| 这份文档 | 源 `docs/MCP_GUIDE.md`,烤进 `src/lib/internal-os/mcp-protocol-md.ts`(`scripts/build-mcp-protocol-md.cjs`) |
| 渲染页 | `/me/agent/protocol` |
| 纯 markdown endpoint | `/api/internal-os/agent/protocol`(无需 session,curl 即可) |

---

## 12. FAQ

**Q: MCP 跟 outbound webhook 必须二选一吗?**
A: 不用,完全可以并用。MCP 走 pull(agent 决定),webhook 走 push(事件发生即触发)。

**Q: token 能不能限制只读?**
A: 可以,见 § 7 Token 权限范围。生成 token 时展开「限制 Token 权限范围(高级)」勾 `read:*`,得到一个纯读 token。

**Q: 能用 stdio transport 不用 HTTP 吗?**
A: 不行。FluxDesk 是 multi-user server,只支持 HTTP transport(每个 request 自带 Bearer token 区分用户身份)。stdio 假设单一用户单一进程,不适用这种场景。

**Q: 我能不能让 agent 替我审批别人的请假?**
A: 技术上可以(如果你是 admin),但**强烈不建议**。审批是宪章 Article 9(作者完整性)所守底的"人做的动作"。让 agent 替你审批等于把用户权益放到不可追溯的位置。如果真要做,至少加一层"agent 提议 → 人 confirm"的中间步骤。

**Q: 离职后 token 自动失效吗?**
A: 是。standard offboarding flow 会 revoke 全部 access credentials,包括 agent token。配在客户端的会立即开始返回 401。

---

## 13. Catalog row slug 命名约定

> v0.19.5 起:`team_support_resources.slug` 是 catalog 行的稳定 ID,REST `/api/v1/catalog/items/{slug}` + MCP `catalog_item_*` 都拿它做主键。每个新 row 都应该按下面的约定起 slug,避免后续维护痛点。

### 13.1 推荐:`<integrator-slug>.<resource>`

新 row 优先用「整合方 slug + 资源名」二段式,中间用半角点分隔:

| 场景 | slug 示例 |
|---|---|
| fd1 的入站 webhook | `fd1.inbound` |
| fd1 的 bastion SSH 凭据 | `fd1.bastion-ssh` |
| Sentry 的告警接入 | `sentry.alerts-channel` |
| OpenAI 的 API key | `openai.api-key` |

好处:

- 一眼看出来这条 row 属于哪个整合方
- `integrator_slug` 过滤时能跟 `personal_inbound_tokens.integrator_id` 对得上
- 卸载整合方时,grep 一个前缀就能列出所有相关 catalog row

> **inbound/bootstrap legacy 语义**:`fd1.inbound`(入站 webhook)、`fd1.bastion-ssh`(堡垒机 SSH 凭据)这类 slug 属于**旧的个人入站接入 / bootstrap 模型** —— 一个 catalog row + 个人 token,由 admin/CLI 逐用户投递。它们**不是** Project Integration 的接入方式:工具要接入某个 Project(拿 project-scoped token、按 allowed_actions 工作)请走 [Project Integration Pair & Token Flow](./current/02_core_flows/20_Project_Integration_Pair_And_Token_Flow.md)(成员申请 → Owner/Admin 审批 → 一次性 pair token → consume → project-scoped token),**不通过堡垒机/broadcast 私发 pair URL**。

### 13.2 历史保留:`<category>-<resource>`

旧 row(v0.19.5 之前手工建的)用的是「类别 + 资源名」单段格式,例:

- `network-vpn-access`
- `saas-figma-account`
- `repo-monorepo-write`

这些**保持原样**(不强制改名),只在新建 row 时按 13.1 起名即可。两种格式同时存在不会冲突 ── `slug` 字段只要求 `^[a-z0-9][a-z0-9._-]{0,79}$`。

### 13.3 `category` 字段

`category` 是路由用的机器 slug,不是显示标签:

- ASCII only,小写,半角连字符分隔
- **新建 row 用机器 slug,UI 自动映射到带 emoji 的显示标签** —— 见下表
- 老 row 里有 emoji-prefixed category(`🌐 网络资源`)的是 v0.16 之前的 legacy 数据,后端会兼容显示(原样透传)但新 row 不应该这样起

**v0.19.9 — 7 个标准机器 slug + 显示标签:**

| 机器 slug | 中文显示 | English display |
|---|---|---|
| `llm-backend` | 🤖 公司付费 AI 订阅 | 🤖 Paid AI Subscriptions |
| `network` | 🔑 接入凭证 | 🔑 Access Credentials |
| `inbound-integration` | 📥 外部工具消息接入 | 📥 Inbound Integrations |
| `outbound-integration` | 📡 出站 Webhook | 📡 Outbound Webhooks |
| `mcp-agent` | 🧠 MCP / Agent | 🧠 MCP / Agent |
| `dev-environment` | 🖥 开发环境 | 🖥 Dev environment |
| `cross-border-comm` | 📞 跨境通讯 | 📞 Cross-border Comms |

实现:`src/lib/internal-os/catalog-categories.ts` 是唯一来源,`/team-support` UI 与 REST `/api/v1/catalog/items` 响应都从它读。

**REST 响应里的两个字段:**

- `category` — 原始机器 slug(整合方应当用这个做 routing / 过滤);如果是 legacy 行则原样保留 emoji 字符串
- `categoryDisplay` — server 的 best-effort 英文显示(给 CLI / 日志 / README 用,非 i18n 客户端不需要自己查表)

整合方拿到响应后,如果是 UI 渲染,**优先用 `category` 机器 slug** + 自己的本地化表;只在没有 i18n 上下文时退回 `categoryDisplay`。

### 13.4 完整 row 创建示例

> 下例是 **inbound 事件接入(个人入站 token)的 bootstrap**,即 fd1-style「一个 catalog row + 逐用户 personal token」旧模型。它**不是** Project Integration 接入:后者不建 catalog row、不发个人入站 token,而是走 [Project Integration Pair & Token Flow](./current/02_core_flows/20_Project_Integration_Pair_And_Token_Flow.md) 的审批 + 一次性 pair token → project-scoped token。

```bash
curl -X POST https://my.fluxdesk.net/api/v1/catalog/items \
  -H "Authorization: Bearer flux-tools-XXXX" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: fd1-inbound-bootstrap-001" \
  -d '{
    "slug": "fd1.inbound",
    "name": "fd1 入站事件接入",
    "type": "api_key",
    "category": "inbound-integration",
    "description": "fd1 → FluxDesk 的入站事件 webhook token",
    "application_guide": "在 /me/integrations 申请;审批后 fd1 receiver 自动拿到 personal token",
    "approval_required": true,
    "is_pinned": false,
    "display_order": 10
  }'
```

返回 201 + 完整的 row(`item.slug` / `item.id` / `item.created_at` 等)。

MCP 等价:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "catalog_item_create",
    "arguments": {
      "slug": "fd1.inbound",
      "name": "fd1 入站事件接入",
      "type": "api_key",
      "category": "inbound-integration"
    }
  }
}
```

### 13.5 用 `id` 替代 slug 寻址(v0.21.4 起)

`team_support_resources.slug` 是可空字段,极少数历史行的 `slug = NULL`,只能靠主键 UUID 寻址。从 v0.21.4 起,以下三个 MCP 工具接受 `id`(uuid)作为 slug 的替代输入,**必须二选一,不可同时给**:

| Tool | slug 字段 | id 字段(新) |
|---|---|---|
| `assign_catalog_to_employee` | `catalog_slug?` | `catalog_id?` (uuid) |
| `catalog_item_update` | `slug?` | `id?` (uuid) |
| `catalog_item_delete` | `slug?` | `id?` (uuid) |

验证规则:

- 同时给 slug + id → `specify exactly one of ...`
- 两个都不给 → `must specify ...`
- 给的 slug / id 找不到活跃行 → `catalog not found or archived`

审计日志(`audit_logs` + MCP 自带 `logAudit`)在 metadata 里同时记录:

- `slug`:解析到的真实 slug(legacy 行可能为 `null`)
- `catalogResourceId`:总是有的 UUID
- `addressedBy`:`"slug"` 或 `"id"`,标记调用方实际用了哪条路径

例:用 `catalog_id` 直接分配一个 slug-NULL 的 legacy row 给用户:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "assign_catalog_to_employee",
    "arguments": {
      "catalog_id": "242fa5c3-1234-4567-89ab-cdef01234567",
      "email": "alice@fluxprimestudio.com",
      "note": "legacy row, addressing by uuid"
    }
  }
}
```

**REST `/api/v1/catalog/items/[slug]` 不动**:那条路由是 slug-keyed URL,本身就要求 slug 存在,legacy slug-NULL 行通过 REST 不可达,因此这个能力**只在 MCP 暴露**。

### 13.6 `assign_catalog_to_employee` 返回 `idempotent` 标志(v0.22.3 起)

`assign_catalog_to_employee` 现在返回 `{ approval_id, status, idempotent }`:

- `idempotent: false` — 这次调用新建了一条 `team_support_requests` row
- `idempotent: true` — 目标用户已经有一条非终态的 (pending / approved / in_execution / completed) row,server 直接复用现有 `approval_id`,**没有副作用**

整合 CLI 渲染 `idempotent=True/False` 替代之前的 `idempotent=?`。fd1 反馈 iter 61 polish B。

### 13.7 `revoke_catalog_approval` 支持 `approval_id` 寻址(v0.22.3 起)

补齐 § 13.5 一致性最后一块:`revoke_catalog_approval` 现在接受 `approval_id`(`team_support_requests.id` UUID)作为 `catalog_slug + email` 的**替代**入参。XOR 校验:`approval_id` 与 `(catalog_slug AND email)` 必须二选一,同给/全空都报错。响应增加 `addressedBy: "slug+email" | "approval_id"`,审计日志 metadata 同步带这个字段(对齐 catalog_item_update / catalog_item_delete 的 `addressedBy` 模式)。

### 13.8 `revoke_catalog_approval` 自动通知用户 + `notify_employee` 开关(v0.24.7 起)

之前 `revoke_catalog_approval` 静默撤销,用户要等下次 401 才察觉。这条不对称:`assign_catalog_to_employee` 在授权时就走 inbox + push notify。fd1 UX#8 #15 把撤销侧补齐。

**新行为**:撤销成功且实际有改动(`revoked=true`)→ 自动给目标用户写一条 `notifications` row + 触发 web push(沿用 `notifyActivityPush` 通道,跟 assign 走同一套):

| 字段 | 值 |
|---|---|
| `title` | `🔒 资源已撤销 · ${catalog.name}` |
| `body` | `${actor.name} 已撤销你对「${catalog.name}」的访问权 · 在 /team-support「我的权限资产」查看` |
| `url` | `/team-support?tab=assets` |
| `tag` | `team-support-revoked-${requestId | assetId | catalogId}` |
| `type` | `system.notice` |

Charter Art 7:body **只**带 catalog 名称 + actor 显示名,**绝**不带 token 值或任何敏感字段。

**新入参 `notify_employee?: boolean`**(default `true`):

- 不传 / `true` — 走通知(常态)。
- `false` — 跳过 inbox + push。用于批量 offboard、迁移脚本、用户已经 inactive 或通过其它通道告知的场景。

幂等 re-call(`revoked=false`,什么都没变)**无论如何**都不通知,避免重复打扰。

REST `DELETE /api/v1/catalog/approvals` / `POST /api/internal-os/integrations/catalog/approval` 这两条 REST 路径也走同一 `revokeCatalogApprovalCore`,默认同样开通知;现暂无 query 参数透出 `notify_employee`(MCP-only 入参),后续按需补。

### 13.9 `revoke_catalog_approval` 级联吊销行为(v0.24.8 起,文档化)

`revoke_catalog_approval` 的副作用范围跟 catalog 行的类型挂钩 —— 之前一直是隐含行为,这里讲清楚,免得整合方误以为「revoke 一行就一定砍掉所有用户的接入 token」。

| Catalog row 类型 | Token 级联 |
|---|---|
| `tools-claim-*`(`type='api_key'` + 通过 `issue_admin_tools_claim_token` / `linked_tools_token_id` 关联了 tools-claim row) | **级联**吊销所有从这个 catalog row mint 出来的 `personal_inbound_tokens` 行(目标用户通过该 token 走的 inbox channel 会立刻 401 `token_revoked`) |
| 其它 catalog 类型(普通 access asset、文档链接、人工流程等)| **不**做 token 级联;只置 access asset `revoked_at` |

响应里的 `tokens_invalidated` 计数 = 这次级联实际吊销的 token 行数(级联不发生时为 `0`)。配合 `revoked: boolean` 看一次调用整体效果:

- `revoked=true, tokens_invalidated=0` — access asset 撤了,但这条 catalog 不属于 tools-claim 类(没有 token 可级联)
- `revoked=true, tokens_invalidated=N` — access asset 撤了 + N 条 personal token 同步吊销
- `revoked=false, tokens_invalidated=0` — 幂等 re-call,什么都没变

整合 CLI 在 batch offboard 时建议**先**调 `list_inbound_tokens({owner_email})` 拿到用户自己 mint 的其它 token,**再** `revoke_catalog_approval`,否则只看 `tokens_invalidated` 数字容易误判「这个用户是不是真的所有接入都干净了」。

### 13.10 `approve_team_support_request` 响应字段 canonical 化(v0.24.7 起)

fd1 UX#8 #16:审批通过分支返回的 access asset id 字段名跟数据库列 (`access_assets`) 对不上,叫 `asset_id` 容易跟 task asset / file asset 等其它"asset"概念混。

**新响应**(单 row 分支):

```json
{
  "request_id": "...",
  "status": "approved",
  "access_asset_id": "uuid",   // canonical · 跟 access_assets.id 一致
  "asset_id": "uuid"            // deprecated alias · ONE release 后移除
}
```

Bundle 分支的 `children[].asset_id` 同样增补 `children[].access_asset_id` canonical 字段,deprecated alias 同样保留一个 release。

跟 v1.6 `status`/`new_status` 同款 deprecation 节奏:整合方先切到 canonical 字段,下个 release 我们就移除 alias。

---

## 14. Changelog

### v1.40 · 2026-10-28 (凭完整 FluxDesk ID 加好友)

- `search_personal_contacts` 也接受完整 FluxDesk ID,并且不再要求共同 Project(完整邮箱同样如此);ID 每天 20 次。
- 新增 `send_friend_request`(`write:chatter`,只限本人个人 Token):按完整 ID 或邮箱发送好友申请,支持 `dry_run` 与 `idempotency_key`,对方同意后才成为好友。
- 新增 `set_my_fluxdesk_id_discovery`(`write:profile`)与 `get_my_profile.fluxdesk_id_discoverable`:隐私开关「允许别人通过 ID 找到我」。

### v1.39 · 2026-10-25 (里程碑全部可经 MCP 管理)

- 新增 `milestone_list` / `milestone_get`(`read:projects`)与 `milestone_create` / `milestone_update` / `milestone_archive` / `milestone_items_set`(`write:projects`):与网页里程碑同一服务(同一权限、模块开关、审计与通知);写入带 `client_message_id` 与 `dry_run`,编辑带 `expected_version`;AI 写入署名「<AI> 代 <本人>」(MCP:MILESTONES-1025-R1,平台反馈 7c24408b)。

### v1.38 · 2026-10-25 (托管节点执行任务的 MCP 工具)

- B2.6.2 (wbnav-1026)：新增只读工具 `list_my_delivered_records`(scope `read:tasks`) 与 `list_my_workbench_updates`(scope `read:inbox`)，分别对应 工作台 › 我的工作 › 已交付 与 工作台 › 动态；均只读调用人自己的视图。新增 `retire_my_workbench_draft`(`write:tasks:draft`)，私有草稿软删除，同一 App 命令、预览、草稿绑定幂等键和 AI 代本人归属。
- `get_project_assistants` / `set_project_assistant_service`:等待节点与已失败请求的 `waiting_for_node` / `can_cancel_request` / `can_switch_to_fluxdesk_hosted` / `switch_needs_node` / `hosted_available`,命令 `cancel_request` / `switch_to_fluxdesk_hosted`(HOSTCAP-1025,s100)。

- 新增 §2.5:`hosted_job_feed` / `hosted_job_get` / `hosted_job_claim` / `hosted_job_report` / `hosted_job_events` / `hosted_job_open_question` / `hosted_job_decisions` / `hosted_job_decision_ack` / `hosted_job_result` / `hosted_job_artifact_file` / `hosted_job_repository_credential` / `hosted_heartbeat`,只为托管节点自己的 Project 凭据注册,行为与错误码同 runner 接口(HOSTWIRE-CORE-1025)。

### v1.37 · 2026-10-25 (Agent 连接 · declared scopes enforced)

- 新增 OAuth 一键授权(§2.4,ADR-0047):scopes `read` / `write`;同意页选择只读/读写、项目、过期;授权码一次性;`/agents.md`。新增 `fluxdesk_help`(任何 token;从这里开始)、`list_my_agent_connections` / `get_my_agent_connection_activity`(`read:meta`)、`revoke_my_agent_connection`(`write:profile`)。
- 每个工具声明的 `requiredScopes` 在运行前对每一种 token 强制执行(§7,MCPSCOPE-1025):缺 scope 回 `forbidden` + `required_scopes` + `workflow_status.next_action`;`fluxdesk_help` / `whoami` / `ping` 免 scope。`search` 需所搜类型的任一读 scope;`create_feedback` `write:feedback`(`as_draft` 时 `write:feedback:draft`);`execute_action` `read:tasks` 或 `wms:write`(再校验所选动作的 scope)。`add_task_comment` / `update_task_status` / `add_task_cc` / `remove_task_cc` / `set_workitem_delivery_target` 不对 Project 助手开放(用 `ask_human`)。

### v1.36 · 2026-10-24 (托管助手 in 我的空间)

- 新增 `get_my_ai_assistant`(`read:meta`)与 `save_ai_assistant_memory`(`write:profile` 或 `agent:dispatch`):你的托管助手放在你的我的空间。记忆分两处:你自己的偏好/指示/参考在我的空间;从项目 X 读到的内容留在 X(离开 X 即删除,X 删除也随之删除)。在项目 X 里做事仍由 X 决定(X 对助手的授权 + 你在 X 的成员身份);高影响操作由助手发起时转到你的「需要我」;X 的审计记「由 <名字> 的 Agent」。

### v1.34 · 2026-10-24 (connected systems · friends · admin overrides · Project modules · AI connections)

- 新增 `list_project_connected_systems` / `get_project_connected_system`(`read:projects`,仅项目活跃成员):一个 Project 已连接的系统与状态,系统详情含最近 20 次推送及被拒原因(只记元数据,未知 Token 不记)。
- 新增 `search_personal_contacts`(`read:chatter`)与 `report_personal_contact`(`write:chatter`,只限本人个人 Token);`respond_personal_contact_request` 接受时可传 `contact_period`(`temporary_90d` 默认 / `temporary_7d` / `long_term`,不传与以前相同 = 90 天)。
- 平台角色(admin / super_admin / hr_admin)本身不再让 MCP 进入 Project 内容:`update_task_status`、`get_project_summary`、`list_project_repositories` 需项目活跃成员;`trigger_next_step` 中非该 Agent 提供者的 super_admin 也需成员;`resolve_inbound_event` / `supplement_inbound_event` 只限原发送方(与 REST 相同);入站 `project_slug` 归属只认活跃 owner/lead。被拒与不存在的回复相同。
- `create_meeting` 关联工作项(`task_id` / `task_code`):不存在、看不到或不在本项目的工作项一律返回 `task_not_found`。
- 新增 `get_project_modules` / `check_project_module_operation`(`read:projects`)与 `set_project_module_enabled`(新 scope `write:projects`,只限 Project 管理员/协调员):Project 功能与页面,目前只有「里程碑」可开关;新增 `list_my_ai_connections`(`read:meta`)/ `stop_my_ai_connection`(`write:profile`),只限本人个人 Token:我的 → Agent 与授权。

### v1.33 · 2026-10-24 (decision chain · personal inbound self-grant)

- 新增 `get_decision_chain`(read:decisions)与 `append_decision_outcome` / `append_decision_review` / `set_decision_fields`(新 scope `write:decisions`)：读一条决策的只追加历史，追加结果 / 复盘，设置可选的把握 / 预期结果 / 复盘日。全部经统一权限判断；做决定仍只由人完成(ratify / ask_human 回答)。
- 个人接入权限(`inbound-integration-access`)：`get_personal_inbound_access` 新增 `self_grantable`；资源目录行 `member_self_grant` 为 true 时，公司成员调用 `request_personal_inbound_access` 直接回 `granted`(false / null = 仍由管理员审批，所有部署相同)。`catalog_item_update` 接受 `member_self_grant`(true / false / null)，资源目录 DTO 新增 `member_self_grant`。
- `create_task` / `create_meeting` 的 `app_url` 使用本部署对应 App 的链接前缀：my.fluxdesk.net 为 `fluxdesk-familiar://`，TEST 为 `fluxdesk://`；app.fluxdesk.net 没有 App，不返回 `app_url`。不写项目、也没指定别人时，待办 / 日程落在自己的「我的空间」(`project_slug: "project0"` 可显式指定);有关联 / 父工作项时用它所在的 Project;指定了参会人、执行人、审核人或抄送时在团队 Project 里推断，「我的空间」不作为候选。

### v1.32 · 2026-10-23 (quick `create_task` / `create_meeting`)

- `create_task` 只需 `title`:不写项目时,只有一个可创建的 Project 就直接用,多个返回 `status: "needs_choice"` 和候选(≤10,只含自己是成员的 Project)。不带偏移的 `due_date` 日期时间按 Project 时区理解。
- `create_meeting` 新增 `project_slug`(slug 或项目名,同样推断)、`participant_names`(只在该 Project 当前成员里找,不唯一 → `needs_choice`,找不到 → `participant_not_found`);不带偏移的 `starts_at` 按 Project 时区(或 `timezone`)理解;不填参会人即只给自己的日程(Project Agent 提案除外);直接创建也认 `idempotency_key`。
- 两个工具成功时都多返回 `web_url`、`app_url`(App 深链)和一句 `summary`。旧字段保留;权限、scope、提案路径、审计不变。
- 对旧调用有三处行为变化:① `create_meeting` 不带偏移的 `starts_at` 以前按创建者本人时区理解,现在按 Project 时区(传了 `timezone` 则按它,以前直接创建会忽略 `timezone`);`create_task` 不带偏移的 `due_date` 日期时间以前按服务器时区,现在按 Project 时区。② 直接 `create_meeting` 带 `idempotency_key` 以前被忽略,现在同 key 同请求重放同一会议、同 key 不同请求返回 `idempotency_conflict`(不要复用固定 key)。③ `project_slug` 同名匹配到多个 Project 时以前任选一个,现在返回 `needs_choice`。夏令时跳过的不存在时刻会被拒绝。

### v1.31 · 2026-10-22 (`close_work_item`)

- 新增 `close_work_item`：关闭（取消）一个工作项，必填原因、可选 `idempotency_key`，与 Web「关闭这个工作项」及 `POST /api/v1/projects/{projectId}/work-items/{workItemId}/close` 同一 canonical 命令（同一权限、同一审计记录、同一通知）。结果 `outcome: "closed"`，与 `done` 分开，不计入完成统计。

### v1.30 · 2026-10-14 (`get_meeting` returns legacy links)

- `get_meeting` 新增 `meeting_url_legacy`：规则之前存下、现行加入链接规则会拒绝的旧链接不再返回 `null`，而是按原样返回并标 `meeting_url_legacy: true`，Agent 据此知道会议有链接；`update_meeting` 原样重发即保留(仍接受 `null` 作为同一旧链接的回显)。脚本协议的存值仍返回 `null`。

### v1.29 · 2026-10-13 (meeting edit: `update_meeting` + `get_meeting`)

- 新增 MCP write tool `update_meeting`，scope `write:meetings`：编辑标题、议程、地点、会议链接、关联工作项，改期(`starts_at` + `duration_minutes`)，增删参会人。与 Web 编辑面板和 App 原生「编辑」共用服务端 `meeting-update-command/1.0`(`src/lib/internal-os/meetings/update-command.ts`)，写入、audit 与通知与 Web 完全一致。
- 必须提供 `expected_version` 与 `idempotency_key`：版本过期返回 `version_conflict`(附 `current_version`)，同 key 重放返回原回执且不重复通知。所有行写入在一个事务内，通知在提交后发送。
- Project Agent 凭据不能直接编辑已有会议，且不在 Project-scoped token 的工具白名单内。
- 新增只读工具 `get_meeting`(scope `read:projects` 或 `write:meetings`)：返回会议版本、时间、地点、https 链接、组织者、参会人与 RSVP、关联工作项及 `can_edit`，可见性与 App 会议详情一致；Agent 用它取得 `expected_version`。
- `meeting_url` 规则统一(`src/lib/internal-os/meetings/meeting-url.ts`)：新建与改动的链接只接受 http(s)，Web / App / MCP / 提案创建同一规则；编辑时原样重发已存的旧链接(规则之前的「腾讯会议 123 456」、`zoommtg://…`)保留不动，不阻止改标题、议程、地点；脚本协议即使未改也拒绝。`get_meeting` 按同一规则返回 http(s) 链接，非链接文字返回 `null`。

### v1.28 · 2026-10-09 (personal work-time read)

- 新增只读工具 `get_my_work_time`(scope `read:tasks`)，按天/周/月/年或一段连续日期范围，返回调用人自己的 canonical 工时明细与 `draft` / `returned` / `submitted` / `confirmed` 分状态汇总。
- 可按 Project 或 WorkItem 过滤；按 WorkItem 查询时会返回同一 WorkItem 的 `effort_estimate_hours`，便于将实际工时与估时对照。
- 工具复用 Web / App 的 work-time statement aggregation，不读已退役的 work-time entry 层；只返回 token owner 本人数据，Project-scoped token 不能跨越绑定项目。

### v1.27 · 2026-07-30 (WorkItem participants visibility + narrow create scope)

- `create_task_draft` 与 `create_task` 新增可选 `visibility: "project" | "participants"`；省略值保持 `project`，未知值拒绝。
- `participants` 会原样贯穿 draft receipt、`workitem.publish` proposal、human adoption、direct-create、audit 与 idempotency replay。相同 key 改变 visibility 会 conflict，不会静默放宽。
- 新增 `write:tasks:create` 最小 scope，只允许 direct create；既有 `write:tasks` / `write:*` / `*` 继续单向覆盖它，窄 scope 不能反向获得 Task mutation 或 publish authority。

### v1.26 · 2026-06-29 (project-scoped MCP meeting creation)

- 新增 MCP write tool `create_meeting`，scope `write:meetings`。入参必须包含 `project_id`，并且至少有 1 个非创建者参会人(`participant_user_ids` 或 `participant_emails`)。`task_id` 可选；`task_code` 可选；传入时必须属于同一 `project_id`，会议会持久关联到该工作项。
- 服务端复用 Web 会议创建校验:调用者必须能看到项目，参会人必须属于同项目，创建者自己创建的未取消会议时间段不能重叠。
- 成功写 `meeting.created` audit + 邀请通知；失败统一走结构化 `{ ok:false, error:{ type,title,detail,field? }, error_code, message }` 形态，重叠场景返回 `conflict / creator_time_overlap`。

### v1.24 · 2026-06-26 (employee connector onboarding hardening)

- Claude Desktop custom connector docs now spell out TEST / PROD URL selection, blank OAuth Client ID / Secret fields, token placement mistakes to avoid, Anthropic cloud reachability, and `ofid` / reference-id recovery steps.
- Codex CLI snippets remain pure zsh-safe command blocks with no markdown comments or numbered instructions; token storage uses `FLUXDESK_MCP_TOKEN` via `--bearer-token-env-var`.
- Claude Code, Cursor, and custom-client guidance now explicitly separates URL, bearer token, JSON config, shell command, and JSON-RPC verification steps.

### v1.21 · 2026-06-05 (`create_task` 授权身份 assignee + 一次性 reviewer 接入)

fd1 9984c150 (Will) — MCP `create_task` 现在按 caller 权限把 assignee / reviewer 设到合适身份,不再静默 fallback 到 token owner。

- 新增入参 `assignee_user_id?: uuid`(优先级高于 `assignee_email`,防 stale email 缓存)。
- 新增入参 `reviewer_user_ids?: uuid[]`(≤50,与 `cc_user_ids` 同语义:inactive / self / unknown ID 静默丢弃;一次性写入 `task_reviewers` 加 reviewer notify push)。
- 权限 gate 走新 helper `canAssignTaskTo(caller, targetUserId, projectId)`:`super_admin` / `project_owner`(`effectiveProjectRole`)/ 项目 `tech_lead_id` 通过;其余角色返 typed 错误 `{ error: { type: "forbidden", title: "permission_denied", detail: "...missing_capability=task.assign...suggested_action=..." , field: "assignee_user_id" | "assignee_email" } }`。
- `assignee_user_id` 解析失败 → `{ error: { type: "not_found", title: "Assignee user_id did not resolve", detail, field: "assignee_user_id" } }`(不再静默 self-assign)。
- `as_draft=true` 草稿 round-trip `assignee_user_id` + `reviewer_user_ids`,publish-time 再校验(草稿期允许暂存未授权配置)。
- 成功响应新增 `reviewers: [{ id, email }]` echo,跟 `cc` 字段同 shape,caller 可 sanity-check 实际落表名单。
- 历史 `assignee_email` 静默 fallback 行为不再存在 — 这是 breaking change 对应 fd1 池里 Will 的诉求(「不要创建到错误负责人再让人工修改」)。
- 测试:`src/__tests__/mcp-create-task-authorized.test.ts` 19 case = 12 source-scan + 7 helper unit(5 role × 2 scenario + 1 cross-project 守门 + 1 export shape 守门)。

### v1.22 · 2026-06-09 (FluxDocs tag tools aliases)

- 新增显式 FluxDocs tag 工具:`list_tags`、`search_by_tags`、`tag_doc`、`untag_doc`。
- `list_tags` / `search_by_tags` 使用 `read:docs`，只返回当前调用者可见文档贡献出的标签和结果。
- `tag_doc` / `untag_doc` 沿用 docs 写 scope：草稿走 `write:docs:draft`，已发布文档走 `write:docs:publish`；归档文档需先恢复再编辑标签。
- `tag_doc` 默认 `mode=append`，不会删掉原有标签；需要完全替换时显式传 `mode=replace`。
- `untag_doc` 要么传 `tags` 删除指定标签，要么传 `remove_all=true` 清空全部标签；空输入会被拒绝。

### v1.20 · 2026-06-03 (一键添加 Phase 1)

- `/me/agent` 的 Guided setup 对 Claude Desktop / Cursor / Cline / Aider 显示「一键添加」按钮,打开 `fluxdesk://connect` deep link。
- 一键添加生成的 token 在 Agent Token 列表中显示「通过一键添加」标记,撤销按钮显示「撤销接入」。复制配置 / 下载配置继续作为所有客户端的 fallback。
- 入口从头像菜单提升到左侧栏底部固定显示的「AI 助手 / Agent 接入」,避免关键接入能力藏在身份菜单里。

### v1.19 · 2026-06-03 (AI Agent onboarding Phase 1 边界)

- `/me/agent` 文档同步 Phase 1 现实边界:Guided setup 是 deterministic state machine,不是 LLM 对话。
- 补充 quickstart cards、copy/download config、read-first token prompt、现有 token activity 作为当前可用接入体验。
- 明确 per-agent identity schema、deep links、AI-for-AI token mint / scope change / rotate / archive 仍是 RFC,且必须经用户 UI 确认和安全审查后才能 ship。

### v1.18 · 2026-06-03 (AI 文档术语收口)

- 明确用户侧文案使用 **AI 助手 / Agent 接入**,MCP 只在开发者协议、客户端配置、tool name、JSON-RPC 和 scope 语境里使用。
- Token quickstart 补充:全权限 token 只适合短期调试,不要长期在多个客户端复用。

### v1.17 · 2026-06-02 (AI 助手 / Agent 接入 framing)

- 用户侧命名改为 **AI 助手 / Agent 接入**,把 MCP 保留为开发者协议名和客户端配置名。
- `/me/agent` 现在是自助生成 Agent Token 的入口;文档移除旧的审批前置步骤。
- 新增安全快速开始:先读不写、写操作先草稿后确认、按需收窄 scope、定期审计 / 撤销 token。

### v1.17 · 2026-06-05 (代码库绑定改走 per-project 授权 + 新 `code:repo:bind` scope)

- `bind_project_repository` / `unbind_project_repository` 不再以 platform `isAdminRole` 平台管理员身份为唯一 pass 条件;改走 `userCanManageRepoBinding(user, project_id)` 的 per-project gate(super_admin / Project Lead / Tech Lead / `code.repo.bind` 能力)。来源:kingston feedback 855a8a93(INBOX 2026-06-05 issue #2)——「项目负责人 / 技术负责人在自己项目里完成绑定」。
- 新增 token scope `code:repo:bind`,专门覆盖上述两个工具;旧的 `admin:projects` 仍然作为 fallback 接受,已签发的 admin token 不需 re-mint。
- 文档术语对齐:`bind_project_repository` / `unbind_project_repository` 不再写「Admin-only」,改为列出真正的 per-project 授权条件。

### v1.16 · 2026-06-01 (项目代码库与代码动态 MCP)

- 新增 `bind_project_repository` / `list_project_repositories` / `unbind_project_repository`，用于管理项目与 Gitone / Gitea / GitHub 代码库绑定。
- 新增 `list_project_repository_commits`，按项目成员权限读取已同步 commit 摘要，便于 AI 在项目上下文里理解近期代码变更。
- Scope 表新增 `read:projects` 和 `admin:projects`；绑定 / 解绑仍是 admin 操作，读取项目 commit 摘要按项目成员关系控制。

### v1.16 · 2026-06-01 (系统固定起稿 + 负责人需求模板)

- `get_owner_intake_templates` 响应结构不变，但语义调整：`description.label` / `description.body` 是系统固定起稿，不再来自负责人或 admin 可编辑默认项。
- 负责人只维护 `checklists` / `templates` 里的需求模板；负责人没有模板时返回系统默认模板。
- 调用方应把系统起稿当作 scaffolding，结合真实上下文和负责人模板改写后再创建需求。

### v1.15 · 2026-05-31 (历史：接单偏好自动起稿语义)

- 当时 `get_owner_intake_templates` 曾支持负责人 / 系统可编辑默认主题和默认描述。v1.16 起已收敛为系统固定起稿。
- Web 创建需求表单会在选定负责人后自动载入这两个字段；MCP agent 应采用同样语义，先尊重接单人的起稿偏好，再结合上下文改写成真实需求。
- 多条 `checklists` 只作为额外需求模板 / checklist 使用，不再把唯一的标题 / 描述模板混进插入清单。

### v1.14 · 2026-05-30 (历史：负责人接单偏好模板)

- 新增 MCP read tool **`get_owner_intake_templates`**(scope `read:tasks`,NEW v0.25.37)。v1.16 起只把负责人个人内容放在 `checklists` / `templates` 里。
- 返回中会标明 `description_source` / `checklist_source`：`owner` 表示负责人个人维护，`system` 表示 admin 设置的系统默认兜底，`none` 表示暂无内容。
- `create_task` 文档补充：给别人创建需求前建议先调这条读工具，把返回内容作为 `description` 起点，再由人确认后创建。这个流程用于尊重执行者、减少模糊交接，不增加强制审批字段。

### v1.13 · 2026-05-27 (FluxDocs MCP draft / publish / archive)

- 新增 FluxDocs 读写工具:
  - `list_docs`
  - `get_doc`
  - `search_docs`
  - `list_doc_drafts`
  - `create_doc_draft`
  - `update_doc_draft`
  - `publish_doc_draft`
  - `archive_doc`
- 草稿默认不对用户公开；发布仍守 author / admin / assistant / lead 权限。
- `archive_doc` 会级联归档子文档，和 web UI 行为保持一致。
- 新增 docs scopes:
  - `read:docs`
  - `write:docs`
  - `write:docs:draft`
  - `write:docs:publish`

### v1.12 · 2026-05-23 (新增 `search_user_by_username` · 多域名 lookup)

- 新增 MCP tool **`search_user_by_username`**(scope `admin:users`,NEW v0.24.12)。按 email 局部(`@` 前)服务端 ILIKE 查 users,返回 0+ 条 + 每条带 `domain` 字段。整合 CLI 不用再硬编码 `@fluxprimestudio.com` —— v0.24.x 放开 `COMPANY_EMAIL_DOMAINS` 同时允许 `@zohomail.com` 之后,光按 username 加固定后缀拼邮箱会漏掉用 zoho 域名注册的用户。
- 默认 `only_active=true`(过滤 `status='active'`),避免 CLI 误选 offboarded 行;passwd/hash/token 不返。
- Edge cases:空结果 → `{items: []}`(不是 422,合法);`username` 含 `@` → 422 `username_contains_at`;`limit=20`(单 username 在多域名下命中数永远是个位数,够用)。

### v1.11 · 2026-05-23 (新增 `invite_user` · prod onboarding gap 修复)

fd1 UX#15 #35 P1 blocker:

- 新增 MCP tool **`invite_user`**(scope `admin:users`,NEW v0.24.11)。包装 `createUserCore`(从 `/users` 表单的 `createUserAdmin` server action 抽出来的 typed core),给整合 CLI 一键拉用户进 fdesk —— 之前必须打开浏览器走 /users 表单,这是 prod onboarding 的硬阻塞。
- 入参全套 enum: `email` / `name`(必填) + `role?`(`member`/`admin`/`hr_admin`/`super_admin`/`assistant`,默认 `member`)/ `location?`(`china`/`overseas`,默认 `overseas`)/ `ui_language?`(`en`/`zh`/`zh-TW`/`ja`,默认 `zh`)/ `status?`(`active`/`offboarding`/`offboarded`,默认 `active`)。 `employment_type` 已于 2026-10-05 移除(Owner 决定:产品不记录、不展示、不默认用工形式,也不用它做权限);旧客户端仍传该参数时会被静默忽略——调用照常成功,但不会记录任何用工形式,不要据此认为已登记为 contractor。
- 返回:`{ user_id, email, name, status, invite_sent: true }`。
- 同时新建对应 onboarding 清单 + 通知偏好 row + 给新用户 inbox 发欢迎 push —— 跟 web 表单完全同款 pipeline,只是 `audit_logs.metadata.viaMcp = true` 区分入口。
- 错误信封:`domain_not_allowed`(域不在 `COMPANY_EMAIL_DOMAINS` 白名单)+ `user_already_exists`(邮箱已存在,响应体附 `user_id` 让 CLI 选择是否当 idempotency hit)。两者都走 § 8.4 文档化的 problem+json 形态。
- 新 scope `admin:users`:跟 `admin:catalog` / `admin:tokens` 保持同样的拆分原则(kept narrow,不解锁 catalog CRUD 或 token mint;`admin:catalog` 走 one-way superset 自动兼容已签发 token)。
- Charter 守底:Art 9 — audit `actorId = caller`(super_admin 拿着 bearer token);Art 7 — 响应回显 email/name OK(admin 上下文);Art 11 — 双重 gate(scope + `isAdminRole`)。

### v1.10 · 2026-05-23 (新增 `list_approvals_for_user` · offboard 真实清单)

fd1 UX#14 #34:

- 新增 MCP tool **`list_approvals_for_user`**(scope `admin:approvals`)。给 offboard CLI 按 email 枚举用户的全部 approval row(filter by status:`approved` / `revoked` / `pending` / `all`),取代之前硬编码的 catalog 列表 —— 之前每加一行 catalog,fd1 offboard 就漏一项,会留 orphan approval。
- Status 语义跟 request-side 对齐:`approved` = `approved` / `in_execution` / `completed`(mirror `revokeCatalogApprovalCore` 的 live-access 定义);`revoked` = `withdrawn` / `rejected` / `cancelled`;`pending` = `pending`。
- 返回字段:`approval_id` / `catalog_slug` / `catalog_name` / `status` / `created_at` / `decided_at` / `decided_by`(reviewer email)。**不**返回任何 token 值或 secret(Charter Art 7)。
- `user_not_found` 走 v1.9 统一的 problem+json 信封(`email` 扩展字段);非 admin 调用直接 `forbidden`。
- 审计:每次调用都进 `audit_logs`(action `mcp.catalog.list_approvals_for_user`,metadata 带 email / status / count)。

### v1.9 · 2026-05-23 (admin write `user_not_found` 统一 problem+json)

fd1 UX#11 #22:

- **`ProblemTypes.UserNotFound`** 新增(stable URI `https://docs.fluxdesk.net/api/v1/errors/user-not-found`)。
- **所有 admin write 工具**邮箱查不到 user 时返回 problem+json shape(status 422,body 带 `email` 扩展字段)代替原 flat-string `{"error":"user not found"}`。覆盖 MCP `assign_catalog_to_employee` / `create_team_support_request_on_behalf` / `revoke_catalog_approval`(slug+email 寻址路径)+ REST `POST` / `DELETE /api/v1/catalog/approvals`。
- **未触及**:`approve_team_support_request` / `catalog_item_update` / `catalog_item_delete`(都不做 email-based user lookup,通过 `request_id` / `slug` / `id` 寻址)。`approval_id` 寻址下的 revoke 也保留 flat-string(那是数据完整性问题、没有 caller-supplied email 可回显)。`/v1/tokens/claim` 仍按现状返 403 Forbidden(claim 路径不是 admin-only,刻意模糊 user_not_found / user_inactive 避免列举)。
- 详见 § 8.3。Charter Art 7 合规:所有 emitter 都是 admin-gated,admin 本来就有 user list 访问权,回显 email 是 OK 的。

### v1.8 · 2026-05-23 (inbound polish · errors[] + next_action + cascade docs)

fd1 UX#10 #18 / #20 / #21:

- **`broadcast_event`** `error_envelope` 增 `next_action` 字段 — 每个 code 配一句「下一步该做什么」。整合 CLI 拿到失败时直接给操作员看,不再要 caller 自己查文档。详见 § 8.2。Charter Art 7:只点动作路径,绝不泄露 token / 邮箱清单。
- **`revoke_catalog_approval`** 级联吊销行为文档化 — 之前是隐含规则,现在 § 13.9 讲清楚:`tools-claim-*` 类型 → 级联 personal token;其它类型 → 只置 access asset。`tokens_invalidated` 计数 = 实际级联吊销的 token 数。无代码变化。
- **Inbound schema 校验** 多错一次性返回 — `/api/v1/events` + `/api/internal-os/integrations/inbound/personal` 返回新增 `errors: [{field, reason}]` 数组(全部 zod issue),`reason` / `field` 保留 = 第一条,back-compat 不破。fd1 dev 不再要 5 个 round-trip。

### v1.7 · 2026-05-23 (revoke 自动通知用户 + approve 响应 canonical access_asset_id)

fd1 UX sweep #8 findings #15 + #16:

- **`revoke_catalog_approval`** 撤销成功后**自动**给用户发 inbox + push notify(对称于 assign 侧)。新增 `notify_employee?: boolean`(default `true`),`false` 用于批量 offboard / 已 inactive 用户跳过通知。详见 § 13.8。
- **`approve_team_support_request`** 响应新增 canonical `access_asset_id` 字段(匹配 `access_assets` 表名),deprecated `asset_id` alias 保留**一个 release**。Bundle children 同款。详见 § 13.10。

### v1.6 · 2026-05-23 (task tool API 一致化 + create_task project_slug 必填)

fd1 UX sweep #5 findings #8 + #10:

- **`get_task`** / **`update_task_status`**:`code` 现在是 canonical 入参(匹配响应里的 `task.code`)。`task_code` 仍接受作为 deprecated alias,**下个 release 移除**。两个都给时 `code` wins。
- **`update_task_status`** 响应:新增 canonical `status` 字段(匹配 `get_task.task.status` 命名)。原 `new_status` 保留为 deprecated duplicate **一个 release**,然后移除。
- **`create_task`** 入参:`project_slug` 现在**必填**(非 draft 路径)。kingston 拍板:之前的 optional 是 oversight,不是 by design。缺 `project_slug` 返回 `{error: "project_slug is required"}`。**`as_draft=true` 例外**(草稿允许不完整)。历史 `project_id IS NULL` 行不迁移,enforcement 只对新建生效。

### v1.5 · 2026-05-23 (revoke_catalog_approval 可按 approval_id 寻址)

新增 § 13.7 — `revoke_catalog_approval` 支持 `approval_id` 作为 `catalog_slug + email` 的替代入参,XOR 校验,响应/审计带 `addressedBy`。补齐 v0.22.2 Charter 一致性(assign / item_update / item_delete 已支持 slug-or-id,revoke 是最后一块)。

### v1.4 · 2026-05-22 (assign_catalog_to_employee 返回 idempotent)

新增 § 13.6 — `assign_catalog_to_employee` 响应额外携带 `idempotent: boolean`,标记本次调用是新建 row 还是复用了已存在的非终态 row。fd1 反馈 iter 61 polish B。

### v1.3 · 2026-05-22 (catalog 行可按 id 寻址)

新增 § 13.5 — `assign_catalog_to_employee` / `catalog_item_update` / `catalog_item_delete` 三个 MCP 工具支持 `catalog_id` / `id`(uuid)作为 slug 的替代输入,用于寻址历史 slug-NULL 行。fd1 反馈 242fa5c3。

### v1.2 · 2026-05-21 (添加 catalog ROW CRUD)

新增 § 13 Catalog row slug 命名约定 + REST `/api/v1/catalog/items` 五端点 + MCP `catalog_item_create` / `_list` / `_update` / `_delete` 四工具。

### v1.1 · 2026-05-21 (添加 Token Scopes)

新增 § 7 Token 权限范围。已签发的 token 自动获得 `scopes = ['*']`(向后兼容)。



### v1.0 · 2026-05-21

初版。MCP server 自 v0.10.x 起已经在 prod 跑,但一直只有 `/me/agent` 页面里几个 disclosure 面板做文档;这次抽出独立 spec + 渲染页 + curl-able markdown endpoint,跟 INBOUND_INTEGRATION_SPEC.md 一致。
