关于 pipefy-portal-setup
pipefy-portal-setup 是用于配置组织 Pipefy 门户的技能,涵盖页面、组件、布局和子门户。它说明主门户的搭建流程,以及关联和发布子门户的步骤。
主要功能
- 01列出并创建组织门户
- 02创建和编辑页面及元素
- 03调整页面布局与元素位置
- 04创建、关联和发布子门户
- 05检查门户权限与发布状态
适用场景
- 01初始化组织的主门户
- 02添加页面、表单、链接或组件
- 03在主门户发布子门户
- 04调整门户页面元素布局
MCP 相关
面向 MCP 客户端,技能提供 MCP 参考,并说明用于读取和修改门户结构、页面、元素及子门户的工具。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ pipefy/ ai-toolkit --skill pipefy-portal-setup安装后在 Claude Code 中输入/pipefy-portal-setup确认已加载。
附带文件references/cli.mdreferences/mcp.md
详细介绍
门户设置
MCP 客户端:请阅读 references/mcp.md。CLI 用户:请阅读 references/cli.md。以下示例介绍通用操作参数。
配置组织的 Pipefy 门户:初始化主门户,添加页面和小组件,连接并发布子门户(子门户连接使用 Interfaces GraphQL + internal_api)。
详细参考:docs/mcp/tools/portal.md。功能对照表:docs/parity.md。环境变量:docs/config.md。
适用场景
- “创建我们的公司门户”“列出组织 X 的门户”“发布子门户”。
- 添加或更改门户页面、布局或页面元素(
forms、link等)。 - 在主门户上附加、发布、取消发布或删除子门户。
不要用于:
- Pipe、阶段、卡片或自动化——请参阅
pipefy-pipes-and-cards、pipefy-automations。 - HTML 界面(作为单个 HTML 文档编写、存放在
customCode元素中的独立页面)——请参阅pipefy-html-interfaces。以下元素工具接受customCode元素;切勿将它们用于此类元素。 - 存在门户工具时使用原始 GraphQL——请优先使用以下工具。
- 通过未公开的
createInterfaceGraphQL 初始化门户——始终使用create_portal。
前提条件
- 组织 ID: 可使用 UUID 或来自组织查询或 Pipefy URL 的数字组织 ID(以下示例使用
fixture_ids.py中的虚构 ID123456789)。SDK 会在调用 Interfaces 前解析数字 ID。传递给list_portals/create_portal的组织必须与令牌有权写入的组织相同。 - 门户写入权限: 令牌需要在该组织上具有
create_portal和/或manage_portals权限。许多服务账号仅在其默认组织上拥有 Pipe/卡片范围的权限,因此即使在其他组织上读取成功,门户变更操作仍会返回PERMISSION_DENIED。 - 每个组织只能有一个主门户——
create_portal是幂等的(第二次调用会返回相同的门户 UUID)。
写入前确认访问权限
在错误的组织上读取可能成功,但 Interfaces 写入会失败。在更改页面、元素或子门户之前:
- 使用预期的
organization_uuid调用list_portals。 - 确认令牌适用于该组织(服务账号邮箱与属于其他组织的人类用户不匹配,是常见问题)。
- 优先选择账号拥有
manage_portals权限且在 Pipefy 中具有门户管理员权限的组织(而非仅在来自其他组织的读取查询中拥有canManagePortals)。
如果 update_portal 或 delete_portal 返回 PERMISSION_DENIED,但用户坚持认为组织角色正确:Pipefy 可能要求服务账号对该门户界面执行 joinAsAdmin(Interfaces 变更操作,MCP/CLI 尚未提供)。服务账号写入成功前,用户必须先在 UI(或通过 GraphQL)中针对每个门户 UUID 以门户管理员身份加入一次。
门户的组织方式
| 概念 | 预期情况 |
|---|---|
| 主门户 | 每个组织最多 一个(subType: portal)。使用 create_portal(findOrCreateInterfaceByTemplate)创建。 |
list_portals | 通常只有 一行——仅主门户(筛选条件为 portal)。子门户不会显示在此处。 |
| 子门户 | 独立实体(create_sub_portal)。列在 get_portal → subPortals[] 下。 |
| 主枢纽上的 UI | 创建子门户不会在主页上添加磁贴。必须通过 publish_sub_portal / update_sub_portal_element 在 forms 元素上发布(或附加)。 |
| 终端用户可见性 | published: false 的子门户会存在于 API 中,但在发布前对门户访客不可见。 |
| 公开主枢纽 | 在 get_portal 中,主门户的 published 始终为 true。公开访问 = update_portal(visibility="public"),而不是 published 标志。 |
主门户生命周期
- 对于会重复用于测试的组织,优先对现有主门户使用
update_portal,而不是删除后再使用create_portal。 - 对主门户调用
delete_portal会移除一个接口 UUID;除非先删除孤立子门户,否则它们可能会保留。 - 删除主门户后,
create_portal可能会失败并提示Menu already created(当mainPortal为 null 时,组织菜单状态仍会保留)。恢复方法:删除孤立子门户、联系 Pipefy 管理员/支持团队,或在保留的 UUID 上初始化内容——不要改用原始createInterface,因为这会留下一个骨架主页面("Page",0 个元素)和一个损坏的构建器;幂等的create_portal会持续返回该 UUID。
空主页面
如果 get_portal 显示主页面没有任何元素,请仅使用 title 调用 create_portal_page(请求中不要包含 elements)。API 通常会返回一个包含 约 14 个模板小组件(文本、表单、链接等)的页面。将该页面用于发布槽位和元素测试。请勿在 create_portal_page 中传入 type: subPortal——这会导致创建时验证失败。
架构说明
| 主题 | 规则 |
|---|---|
列表中的 published | list_portals 不会返回 published——请调用 get_portal。 |
| 布局中的子门户 | 即使顶层 subPortals[] 为空,磁贴也可能出现在 pages[].elements[] 下,且类型为 subPortal。 |
| 发布流程 | 对现有的 forms 元素使用 publish_sub_portal / update_sub_portal_element(在 internal_api 上使用 updateSubPortalElement)。create_portal_element 且类型为 subPortal 不能替代发布操作。 |
| 元素元数据 | update_portal_element 会整体替换元数据——每次都要发送完整的 metadata JSON。 |
| 元素数据源 | 每次调用 update_portal_element 都必须传入 data_sources 或 portal_uuid,与元素类型无关;两者都省略会被拒绝。data_sources 会整体替换:[] 会取消元素与其管道或表格的关联;对于没有绑定的元素(link、text),应使用单次调用并传入 []。要保留绑定,请省略 data_sources 并传入 portal_uuid(即你传给 get_portal 的 uuid,其 pages[] 中包含 page_id);元素的 dataSources 和 editable 标志会一并传回。 |
| 元数据键 | forms → name(不是 formId);link → linkName / linkUrl(不是 url / label)。 |
| 布局 JSON | update_portal_page_layout 需要一个行对象数组。每一行都必须有非空的 id、type: "row",且 children 中必须是非空字符串。请从 get_portal 复制目标 pages[].layout。metadata.gridMap 提供的是元素尺寸,而非位置。API 会原样存储这里的任何 JSON,因此包装对象或不完整的行都会替换网格。工具包会在本地拒绝非数组及任何不完整的行,但仍接受表示空页面的 [];切勿使用原始 GraphQL 绕过校验。 |
| 页面网格与元素 | create_portal_element 不会改动布局网格,除非你传入 element_id + layout(现有行加上列出新 ID 的一行);duplicate_portal_element 会追加布局行;delete_portal_element 会保留布局引用,除非你传入 layout(现有行中移除该元素后的布局);孤立的引用可能导致门户查看器出错(HTTP 500)。 |
所需工具
| 操作 | 只读 |
|---|---|
list_portals | 是 |
get_portal | 是 |
create_portal | 否 |
update_portal | 否 |
delete_portal | 否 |
create_portal_page | 否 |
update_portal_page | 否 |
delete_portal_page | 否 |
sort_portal_pages | 否 |
update_portal_page_layout | 否 |
create_portal_element | 否 |
update_portal_element | 否 |
delete_portal_element | 否 |
duplicate_portal_element | 否 |
create_sub_portal | 否 |
update_sub_portal_element | 否 |
publish_sub_portal | 否 |
unpublish_sub_portal | 否 |
delete_sub_portal_element | 否 |
delete_sub_portal | 否 |
元素 type 值(15 种):text、table、field、embedLink、embedVideo、embedImage、button、divider、link、forms、pages、subPortal、automationButton、contentBlock、document。
步骤 — 顺利流程(主门户 + 子门户发布)
-
列出或初始化主门户
操作参数:
list_portals(organization_uuid="123456789")主门户行最多只能有一条。如果没有:
操作参数:
create_portal(organization_uuid="123456789")记录
subType为主门户的uuid。 -
检查结构
操作参数:
get_portal(portal_uuid="<MAIN_PORTAL_UUID>")记录
pages[]、elements[]和forms元素 ID。如果主页面没有任何元素,请先在该门户上运行create_portal_page(仅设置标题),然后再添加小组件。 -
可选 — 添加
forms元素(如果不存在模板化的forms插槽)操作参数:
create_portal_element(page_id="<PAGE_ID>", type="forms", metadata={"name": "Request access", "gridMap": {"height": 66, "columns": 4, "minColumns": 4}})如果
create_portal_element从 Interfaces 返回不透明错误或INTERNAL_SERVER_ERROR,请改为从同一portal_uuid和page_id上已有的链接运行duplicate_portal_element,不要盲目重试创建。 -
创建子门户
操作参数:
create_sub_portal(main_portal_uuid="<MAIN_PORTAL_UUID>", name="Partner hub")记录子门户的
uuid。get_portal会在subPortals[]中列出该门户,且published: false— 在第 5 步之前,主门户中心 UI 不会发生变化。 -
在
forms元素上发布操作参数:
publish_sub_portal(portal_uuid="<MAIN_PORTAL_UUID>", element_id="<FORMS_ELEMENT_ID>", sub_portal_uuid="<SUB_PORTAL_UUID>") -
验证发布状态
操作参数:
get_portal(portal_uuid="<MAIN_PORTAL_UUID>")成功条件:目标
subPortals[].published为true。只有在此步骤之后(且符合门户中心可见性规则),最终用户才能看到子门户。 -
可选 — 将主门户中心设为公开
通过
update_portal将<MAIN_PORTAL_UUID>的visibility更新为"public"(SDK 参数:interface_uuid;MCP 参数:portal_uuid)。
步骤 — 页面、布局和安全编辑
在共享组织的主门户上进行元素/布局实验时,请使用临时页面:
- 使用唯一标题运行
create_portal_page(例如Agent smoke 2026-06-01)。 - 仅在该页面上运行
create_portal_element、update_portal_element、duplicate_portal_element、update_portal_page_layout。 - 检查并批准删除临时页面,然后使用
delete_portal_page。
duplicate_portal_element: element_id、portal_uuid 和 page_id 必须指向已包含源元素的同一页面(在同一页面上复制,不要跨页面)。
update_portal_page_layout: 从 get_portal 读取目标 pages[].layout,并保留其行 ID、子元素 UUID 以及未受影响的行。发送完整数组,只修改预期的顺序/分组;然后重新读取并比较。elements[].metadata.gridMap(height、columns、minColumns)描述的是尺寸,因此无法还原行顺序或分组。调整元素大小时,请保留完整元数据。如果布局不可用,请停止位置编辑,不要猜测。切勿将数组包装在 { "rows": [ ... ] } 中。
MCP:
get_portal portal_uuid="<portal_uuid>"
update_portal_page_layout page_id="<page_uuid>" layout=[<complete edited rows from that page>]
get_portal portal_uuid="<portal_uuid>"
CLI:
pipefy portal get <portal_uuid> --json
pipefy portal page layout update --page-id <page_uuid> --layout '[<complete edited rows from that page>]' --json
pipefy portal get <portal_uuid> --json
带位置设置的 create_portal_element: 读取 pages[].layout,为 element_id 生成 UUID,追加或插入一行 { "id": "<new row uuid>", "type": "row", "children": ["<element_id>"] },并同时传入 element_id 和完整的 layout。如果不提供 layout,元素会被创建,但不会出现在网格中。重新读取并比较。
MCP:
create_portal_element page_id="<page_uuid>" type="link" metadata={...} element_id="<element_id>" layout=[<existing rows>, {"id": "<row uuid>", "type": "row", "children": ["<element_id>"]}]
CLI:
pipefy portal element create --page-id <page_uuid> --type link --metadata '{...}' --element-id <element_id> --layout '[<existing rows>, {"id": "<row uuid>", "type": "row", "children": ["<element_id>"]}]' --json
带清理操作的 delete_portal_element: 读取 pages[].layout,从每一行的 children 中移除 element_id,并将完整数组作为 layout 传入。网格和元素会在一次调用中一起更改,因此不会有行继续指向已删除的元素。如果布局仍列出 element_id,请求会被拒绝。重新读取并比较。MCP 和 CLI 参考资料中的删除调用都包含 layout。
sort_portal_pages: 传入非空的 page_ids 列表,且列表中不得有重复项。如果响应中出现嵌套的 success: false,则视为操作失败。
链接元素元数据(创建/更新,完整替换):
{
"gridMap": { "height": 64, "columns": 4, "minColumns": 4 },
"linkUrl": "https://example.com",
"linkName": "Example link"
}
link 没有绑定,因此更新时也要传入 data_sources: [](CLI --data-sources '[]')。
步骤 — 取消发布或移除子门户
取消发布(保留子门户实体;访客将无法访问):
操作参数:
unpublish_sub_portal(portal_uuid="<MAIN_PORTAL_UUID>", element_id="<FORMS_ELEMENT_ID>")
解除元素连线:检查影响并获得批准后,使用 delete_sub_portal_element。
使用 delete_sub_portal 删除子门户界面(不可撤销);删除前请检查并获得批准。
成功标准
list_portals返回组织的主门户(通常为一行);重复调用create_portal会返回相同的 UUID。- 写入后,
get_portal会显示预期的pages/elements。 - 调用
create_sub_portal后,子门户会存在于 API 中,但在发布前published: false。 - 发布后:
subPortals[].published为true,且主页面会显示已连线的forms插槽。 - 取消发布后:
published为false,但不会删除子门户实体(除非调用了delete_sub_portal)。 - 在临时页面上编辑布局/元素后,生产环境中使用的主门户页面仍可在构建器中打开(不会出现 HTTP 500)。
故障模式
| 症状 | 可能原因 | 恢复方法 |
|---|---|---|
写入时出现 PERMISSION_DENIED | 组织错误、缺少 manage_portals,或 SA 未加入该界面 | 使用与 list_portals 相同的组织;让用户加入门户管理员;尝试使用人工管理员令牌 |
读取正常,但在 admin 组织中写入失败 | 令牌属于组织 A 中的用户,但数字 ID 属于组织 B | 使 organization_uuid 与令牌所属组织一致 |
create_portal 出现 Menu already created | 主门户已删除,但组织菜单状态仍然存在 | 删除孤立的子门户;避免直接调用 createInterface;在现有 UUID 上使用 create_portal_page 进行初始化 |
| 构建器中的主页面为空 | 门户是在 create_portal 模板路径之外创建的 | 使用 create_portal_page(仅标题)创建模板化元素 |
列表中缺少 published | 符合预期 | 使用 get_portal |
get_portal 中有许多子门户,但主界面为空 | 子门户尚未发布到 forms 插槽 | 对每个子门户调用 publish_sub_portal + forms element_id |
| 发布无效 | 元素类型错误,或跳过了 internal_api 连线 | get_portal → forms 元素 → publish_sub_portal |
subPortals[] 为空,但界面显示了磁贴 | 链接位于 pages[].elements 下 | 检查 elements[] 中的 type: subPortal |
create_portal_element 结果不明或出现 500 | 某些组织的 Interfaces 不稳定 | 从同一页面上的现有组件使用 duplicate_portal_element |
| 门户查看器出现 HTTP 500 | 存在孤立的 layout 子项,或布局结构错误 | 从 get_portal 复制/修复布局;删除临时测试页面 |
嵌套的 success: false | API 拒绝了变更 | 读取 error.message;不要仅凭顶层成功状态作判断 |
| 元素验证失败 | 元数据键错误或部分更新 | 提供完整的元数据内容;根据类型使用 linkName / name |
另请参阅
docs/mcp/tools/portal.md— 端点、连线命名、维护者内省pipefy-introspection— 在更改工具前验证 Interfaces / internal_api 变更