关于 service-itsm-agentic-setup-cmdb-access-assign
通过 Salesforce 托管的 Headless-360 MCP 服务,为指定用户分配 CMDB 权限及对应许可证的技能,面向 Service Cloud ITSM 管理场景。它先确认组织级功能已启用,再检查现有授权并验证分配结果。
主要功能
- 01检查组织级 CMDB 功能状态
- 02解析目标用户与权限角色
- 03检测直接及权限集组授权
- 04先分配许可证再分配权限集
- 05验证授权结果并处理重复分配
适用场景
- 01为用户开通 CMDB 只读访问
- 02授予配置项编辑权限
- 03为内容 bundle 管理配置 Type Manager 权限
MCP 相关
该技能通过 Headless-360 MCP 服务的只读与写入工具查询 Salesforce,并分配 CMDB 权限集许可证和权限集。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ forcedotcom/ sf-skills --skill service-itsm-agentic-setup-cmdb-access-assign安装后在 Claude Code 中输入/service-itsm-agentic-setup-cmdb-access-assign确认已加载。
附带文件references/mcp-invocation.md
详细介绍
为用户分配 CMDB 访问权限(Service Cloud ITSM)
通过分配由许可证支持的 CMDB 权限集及其权限集许可证,授予特定用户读取和处理 CMDB(配置管理数据库)数据的能力。每次调用都通过 Salesforce 托管的 Headless-360 MCP 服务(服务键 headless-360)及其四个元工具(discover、describe、dispatch_readonly、dispatch)运行。组织由绑定到当前 MCP 会话的 OAuth JWT 确定——此技能不会处理组织 ID、别名或凭证——因此在生产环境和沙盒中的运行方式完全相同,且无需为每位用户单独安装 MCP。
这是 CMDB 设置的第 3 层。它假设组织级 CMDB 开关已打开(该功能为 ENABLED)——这是另一项技能(service-itsm-agentic-setup-cmdb-configure,第 0–2 层)负责的内容。此技能为用户授予访问权限;它不会为组织启用该功能。
此技能解决的问题
启用 CMDB 功能会打开组织级开关,但某些 CMDB 读取操作(例如 bundleListView)还会检查用户级访问权限。没有 CMDB 权限集的用户仍会收到 403 FUNCTIONALITY_NOT_ENABLED(“未为此用户启用”),即使该功能已在组织级正确设为 ENABLED。此技能会为该用户分配 CMDB 权限集,以确保这些读取操作成功。
如果组织功能尚未启用,此技能不会执行任何操作。 在组织级 CMDB 功能设为 ENABLED 之前,用户级访问权限不会生效。如果发现组织级开关仍处于关闭状态,请停止操作,并告知用户必须先为组织启用 CMDB 功能——请参阅文末的跨技能说明。
四个 CMDB 权限集
每个权限集都是由许可证支持的标准权限集;分配权限集时还需要相应的权限集许可证(PSL),此技能会一并分配。
| 角色 | 权限集(Name) | 对应的 PSL(DeveloperName) | 授予的权限 |
|---|---|---|---|
| 读取者 | ItSrvcCnfgItmReadPermissionSet | ItSrvcCnfgItmReadPsl | 读取 CMDB 配置项 |
| 所有者 | ItSrvcCnfgItmOwnerPermissionSet | ItSrvcCnfgItmOwnerPsl | 拥有/编辑配置项 |
| 类型读取者 | ItSrvcCnfgItmTypReadPermissionSet | ItSrvcCnfgItmTypReadPsl | 读取配置项类型 |
| 类型管理员 | ItSrvcCnfgItmTypManagerPermissionSet | ItSrvcCnfgItmTypMgrPsl | 管理配置项类型 |
对于只读 CMDB 访问,读取者(+ 类型读取者)是最低限度的组合。需要编辑 CMDB 记录的用户应添加所有者;需要管理类型目录的用户应添加类型管理员。如果用户未指定角色,请询问(见“澄清问题”)——对于只需查看的用户,默认分配读取者 + 类型读取者。
管理捆绑包需要类型管理员。 如果用户的目标是安装或管理 CMDB 内容捆绑包(或在读取捆绑包管理信息时遇到
403,例如调用GET /connect/cmdb/bundles/details),则需要类型管理员角色才能解决问题——仅持有读取者/所有者/类型读取者权限的用户,在执行捆绑包操作时仍会收到403 FUNCTIONALITY_NOT_ENABLED。对于需要部署或管理捆绑包的用户,请分配类型管理员(及其对应的 PSLItSrvcCnfgItmTypMgrPsl)。
范围
- 范围内:查找目标用户、解析请求的 CMDB 权限集、检查现有分配、分配权限集许可证和权限集,以及验证分配结果。
- 范围外:启用 CMDB 功能/配置 CMDB 租户(第 0–2 层——
service-itsm-agentic-setup-cmdb-configure)、安装内容捆绑包、对 CMDB 记录执行 CRUD 操作、创建自定义权限集,或更改组织权限/版本。
机制
所有操作都通过 headless-360 MCP 工具分发。读取操作使用
mcp__headless-360__dispatch_readonly,写入操作使用 mcp__headless-360__dispatch——两者都接受原始
HTTP:{"url": "<path>", "method": "GET|POST", "body"?: {...}, "queryParams"?: {...}}——而不是
{operation_id, arguments}。每次调用的准确 url / method / body 请参阅 references/mcp-invocation.md。
这四个工具:
mcp__headless-360__discover——对已索引的操作目录进行语义搜索。此技能使用的标准/query和/sobjects/...REST 路由不一定已编入索引,因此搜索无结果 并不表示该路由不存在——直接分发准确路径(参阅references/mcp-invocation.md)。mcp__headless-360__describe——在任何 POST 之前,获取完整输入架构和规范路由。mcp__headless-360__dispatch_readonly——用于所有读取操作(GET)的分发器。mcp__headless-360__dispatch——用于所有写入操作(POST/PATCH)的分发器。
此技能从不处理凭据——组织已绑定到当前 OAuth 会话。如果 dispatch*
调用返回身份验证错误,请告知用户重新进行 headless-360 MCP 连接的身份验证(并
确认会话指向预期的组织),然后停止。
澄清问题
仅询问无法从对话中推断的信息:
- 是哪位用户? 用户名(或可准确解析为一位用户的姓名/电子邮件)。如果请求是“授予我访问权限”/“针对当前用户”,则解析当前运行用户。
- 需要哪些角色? Reader、Owner、Type Reader、Type Manager,或“只读访问权限”(→ Reader + Type Reader)。如果未指定,查看者默认分配 Reader + Type Reader。如果用户 要安装或管理 CMDB 内容包(或正在修复内容包管理的 403 错误),请包含 Type Manager——没有此角色,内容包操作会返回 403。
- 是哪个组织? 确认目标组织,并明确说明将修改此组织(分配权限集属于写入操作)。对于生产环境,须获得明确确认。
不要重复询问用户已提供的信息;预先填入并注明“(来自对话)”。
工作流
始终先读取再写入:在进行任何分配之前,先执行只读检查。所有分配都按用户进行且具有幂等性——重复分配应视为已完成,而非错误。
步骤 1 — 确认组织级 CMDB 功能已启用(只读,前提条件)
在启用组织级开关之前,用户访问权限没有意义。读取功能状态:
dispatch_readonly({ "url": "/services/data/v67.0/connect/setup/discovery/feature/service-cloud-itsm-cmdb-integration/status", "method": "GET" })
status == ENABLED→ 继续。status != ENABLED(或调用返回403 FUNCTIONALITY_NOT_ENABLED)→ 停止。用 通俗语言告知用户,必须先为组织启用 CMDB,才能向用户授予访问权限,并 引导他们使用 CMDB 功能启用技能(参阅跨技能说明)。不要进行任何分配。
步骤 2 — 解析目标用户(读取)
对于“当前用户”/“我”/“我自己的用户”(不要使用 USER_ID()——它仅适用于 Apex,且会被 REST 查询 API
拒绝;不要依赖 /chatter/users/me 或 /connect/user-profiles/me——
当 Chatter/Communities 关闭时,这些端点会返回 403 FUNCTIONALITY_NOT_ENABLED)。应改为读取 API
根目录并解析身份 URL:
dispatch_readonly({ "url": "/services/data/v67.0/", "method": "GET" })
响应中的 identity 字段是以 /<orgId>/<userId> 结尾的 URL(用户 Id 是最后一个路径
段,并以 005 开头)。直接将该 Id 用作目标用户,或对其进行确认:
dispatch_readonly({ "url": "/services/data/v67.0/query", "method": "GET", "queryParams": { "q": "SELECT Id, Username, Name, IsActive FROM User WHERE Id = '<userId>'" } })
对于指定姓名的用户(提供了用户名/电子邮件):
dispatch_readonly({ "url": "/services/data/v67.0/query", "method": "GET", "queryParams": { "q": "SELECT Id, Username, Name, IsActive FROM User WHERE Username = '<username>'" } })
- 恰好找到一位活跃用户 → 记录其
Id。 - 未找到结果 → 停止;告知用户未找到匹配用户,并请其确认用户名。
- 找到多个结果 → 停止;列出候选用户(Name + Username),并询问应选择哪一位。
- 用户处于非活跃状态 → 向用户发出警告;仍可继续分配,但只有用户处于活跃状态时,访问权限才会生效。
步骤 3 — 解析所需的权限集(只读)
针对每个所需角色,解析其权限集及对应的许可证:
dispatch_readonly({ "url": "/services/data/v67.0/query", "method": "GET", "queryParams": { "q": "SELECT Id, Name, LicenseId FROM PermissionSet WHERE Name = '<psName>'" } })
使用上表中的 Name 值。记录每个 Id(权限集)和 LicenseId(要分配的 PSL)。如果找不到权限集,说明该组织可能未获得 CMDB 许可 — 停止操作,并报告 CMDB 在此组织中似乎尚未设置。
步骤 4 — 检查现有分配(只读 — 幂等性,兼容权限集组)
角色可通过两种方式授予,此检查必须同时接受这两种方式 — 否则会错误地将已授予的角色报告为缺失,并错误地要求分配:
- 直接授予 —
PermissionSetAssignment中的PermissionSetId是该角色自己的权限集。 - 通过权限集组(PSG)授予 — 该角色的权限集是用户已获分配的某个 PSG 的成员。此时,用户的
PermissionSetAssignment行包含PermissionSetGroupId,其PermissionSetId是该组内部的聚合权限集 — 而非成员权限集 — 因此,按PermissionSetId = '<psId>'进行查询会返回 零条记录,尽管用户实际上已拥有该角色。
针对每个角色执行两次权限集读取;任一查询返回 totalSize >= 1,即表示权限集已存在(第二个查询是顶层半连接 — 将其嵌套在 OR 中会导致 MALFORMED_QUERY):
dispatch_readonly({ "url": "/services/data/v67.0/query", "method": "GET", "queryParams": { "q": "SELECT Id FROM PermissionSetAssignment WHERE AssigneeId = '<userId>' AND PermissionSetId = '<psId>'" } })
dispatch_readonly({ "url": "/services/data/v67.0/query", "method": "GET", "queryParams": { "q": "SELECT Id FROM PermissionSetAssignment WHERE AssigneeId = '<userId>' AND PermissionSetGroupId IN (SELECT PermissionSetGroupId FROM PermissionSetGroupComponent WHERE PermissionSetId = '<psId>')" } })
然后检查对应的许可证(包含该角色的 PSG 也会直接分配其对应的 PSL,因此这一次读取已涵盖 PSG 的情况):
dispatch_readonly({ "url": "/services/data/v67.0/query", "method": "GET", "queryParams": { "q": "SELECT Id FROM PermissionSetLicenseAssign WHERE AssigneeId = '<userId>' AND PermissionSetLicenseId = '<pslId>'" } })
如果通过任一方式确认权限集已存在,并且许可证读取返回 totalSize >= 1,则该角色已分配 — 跳过相应的写入操作,并记录为已完成。只分配确实缺失的项目;如果用户已通过 PSG 获得成员权限集,不要再直接分配该成员权限集。
步骤 5 — 先分配权限集许可证,再分配权限集(写入 — 先确认)
先向用户确认目标用户、组织和角色,然后为每个缺失的角色先分配 PSL,再分配权限集:
dispatch({ "url": "/services/data/v67.0/sobjects/PermissionSetLicenseAssign", "method": "POST", "body": { "AssigneeId": "<userId>", "PermissionSetLicenseId": "<pslId>" } })
dispatch({ "url": "/services/data/v67.0/sobjects/PermissionSetAssignment", "method": "POST", "body": { "AssigneeId": "<userId>", "PermissionSetId": "<psId>" } })
201→ 已分配。400 DUPLICATE_VALUE→ 用户已拥有该项;视为成功(幂等),而非失败。- 许可证限额错误(例如
INSUFFICIENT_ACCESS/ 无可用名额)→ 停止为该角色分配;告知用户 CMDB 许可证没有可用名额,以及当前已使用的名额数(名额查询请参见references/mcp-invocation.md)。不要重试。
步骤 6 — 验证(只读 — 不要仅凭 POST 响应判断)
重新执行步骤 4 中的读取 — 包括直接授予和通过 PSG 授予的权限集检查,以及许可证检查。只有通过任一方式确认权限集已存在,并且读取到对应的许可证,才算该角色已完成。逐个角色报告:已分配 / 原本已拥有。只有在此次读取中确认存在的角色才计为已完成。
规则 / 约束
| 约束 | 原因 |
|---|---|
| 分配前确认组织级功能已启用 | 在解除组织级 CMDB 开关限制前,用户访问权限不会生效;先分配只会造成误导,因为操作不会产生实际效果 |
| 写入前将用户解析为唯一一条记录 | 分配给错误用户或有歧义的用户后很难撤销,也会带来安全隐患 |
| 每次分配前读取现有分配 | 分配是针对单个用户的;重复分配会抛出 DUPLICATE_VALUE,因此应跳过已有分配 |
| 先分配 PSL,再分配权限集 | 权限集由许可证支持;用户必须先持有许可证席位,分配才能生效 |
将 DUPLICATE_VALUE 视为成功 | 这表示用户已拥有相应访问权限,是幂等结果,不是错误 |
| 切勿创建或编辑权限集 | 此技能只会分配标准 CMDB 权限集;创建权限集不在范围内 |
| 每次操作前都与用户确认目标组织、用户及写入内容 | 这些操作会在真实组织中进行实际更改,授予用户数据访问权限 |
| 切勿向用户透露内部术语 | 不要在面向用户的输出中提及记录 ID、HTTP 状态码(403/400/…)、API 错误代码(FUNCTIONALITY_NOT_ENABLED、DUPLICATE_VALUE)、对象名称(PermissionSetLicenseAssign)、开发者名称(ItSrvcCnfgItmReadPsl)和工具内部细节(dispatch、headless-360)。使用易懂的角色名称和通俗语言 |
验证清单
- 是否已在进行任何分配前确认组织的 CMDB 功能为
ENABLED? - 是否已将目标用户解析为唯一一条记录?
- 是否已解析每个请求角色对应的权限集及其许可证?
- 是否已在写入前检查现有分配(确保幂等性)?
- 是否已为每个请求角色分配权限集及其许可证?
- 是否已通过写入后的读取进行验证(而非仅依据 POST 响应)?
- 是否已先与用户确认目标组织、用户及每次写入?
输出要求
CMDB Access Assignment — Complete (via service-itsm-agentic-setup-cmdb-access-assign)
Target org: <org>
User: <name> (<username>)
Configuration Item Reader ......... Assigned
Configuration Item Type Reader .... Already had access
This user can now read CMDB data in this org. If they still can't, confirm the org's
CMDB feature is turned on (that's a separate setup step).
面向用户的输出中不得出现内部术语(不得包含记录 ID、HTTP 状态码、错误代码、对象名称或开发者名称)。如果任何步骤失败,请停止操作,并用通俗语言告诉用户哪一步未能成功,以及这对他们意味着什么。将原始错误(例如 403 或 FUNCTIONALITY_NOT_ENABLED)转换为其实际含义(“此组织尚未启用 CMDB”),不要直接重复错误代码。
常见故障(用通俗语言说明)
| 症状 | 可能原因 | 告诉用户的内容 |
|---|---|---|
功能状态不是 ENABLED(或读取状态时返回 403) | 尚未解除组织级 CMDB 门控 | 必须先为组织启用 CMDB;这是单独的设置步骤 — 请参阅功能启用技能 |
| 未找到权限集 | 组织未获得 CMDB 许可/尚未完成设置 | 该组织似乎无法使用 CMDB;请确认已获得许可并启用 |
分配后,在 bundle-management 读取操作(例如 bundles/details、bundleListView)中返回 403 FUNCTIONALITY_NOT_ENABLED,且功能状态为 ENABLED | 用户拥有 Reader、Owner 或 Type Reader,但没有 Type Manager — 捆绑包操作需要此角色 | 分配 Type Manager — CMDB 捆绑包管理必须具备此角色;其他 CMDB 角色不包含捆绑包操作权限 |
| 技能提示缺少 CMDB 角色并要求分配,但用户其实已经拥有该角色(例如通过权限集组获得) | 幂等性检查只查看了直接分配的权限集,未能识别通过组授予的权限 — 第 4 步已修复此问题,现在也会检查组成员身份 | 用户已通过权限集组获得该权限;将此角色视为已授予 — 无需重新分配 |
分配时返回 400 DUPLICATE_VALUE | 用户已拥有该权限 | 这不是错误 — 报告该角色已分配 |
| 分配时出现许可证数量上限/无可用席位错误 | CMDB 权限集许可证席位已用尽 | 报告已用席位数和可用席位数;必须先释放一个席位(或添加更多许可证)才能分配 |
dispatch* 身份验证错误 | headless-360 MCP 会话未通过身份验证/令牌已过期 | 重新验证 headless-360 MCP 连接,并确认会话指向预期的组织 |
跨技能集成
| 情况 | 技能 |
|---|---|
| 尚未启用组织的 CMDB 功能(组织门控仍处于关闭状态) | service-itsm-agentic-setup-cmdb-configure(第 0–2 层 — 先启用功能,然后返回此处) |
| 用户需要安装 CMDB 基础内容捆绑包 | service-itsm-agentic-setup-cmdb-bundle-deploy(第 4 层 — 内容,与用户访问权限分开)。该技能的捆绑包管理读取操作要求用户拥有 Type Manager,因此请先在此处分配该角色 |
参考文件索引
| 文件 | 何时阅读 |
|---|---|
references/mcp-invocation.md | 每项读取和写入操作对应的精确 dispatch* url/method/body、响应封装、许可证席位查询以及错误表 |