内容
# 无浏览器 Penpot 服务器
适用于无浏览器 Penpot 项目/文件/内容管理的便携式 MCP 服务器,
直接使用 Penpot 的 RPC API。无需浏览器,无需 Penpot 插件会话。
此软件包具有 **零项目特定知识** — 无硬编码颜色、
字体或 Penpot 实例 URL。每个使用该软件包的项目都提供其自己的
配置文件和令牌文件,因此可以从 npm 安装或直接复制到
另一个项目中。
## 功能
- 团队/项目/文件/页面 CRUD(`penpot_list_teams`、`penpot_create_file` 等)
- 形状创建 — `rect`、`frame`、`text`、`circle`(椭圆)、`path`、`bool`(布尔运算),
以及 `image` — 通过 `penpot_add_shapes`,颜色/字体解析自字面值或
`{ "token": "name" }` 参考针对您的项目的令牌文件。相同的
token-or-literal 模式适用于间距(布局间隙/填充,
layoutItem 边距/最小/最大尺寸)、圆角半径(`r1`-`r4`)和阴影
(`shadows`,可以是内联 `{ style, color, offsetX, offsetY, blur,
spread, opacity }` 对象或 `{ "token": "name" }`)。形状可以通过可选的
`rotation` 字段旋转(度数,顺时针,关于形状的中心)。
- 形状编辑:`penpot_update_shapes` 在 ID 处更改现有形状 —
位置、大小、旋转、名称、填充、描边、阴影、圆角半径或(对于文本)内容/字体。
只有您传递的字段会被触摸;其他所有字段(子项、组件/变体标签、布局)保持不变。
`clearStroke`/`clearShadows` 完全删除描边/阴影。几何更改会自动重新计算形状的
选择框和变换,因此部分编辑(例如仅 `width`)不会与形状的其余几何结构脱节。
布局/layoutItem 无法通过此方式编辑 — 只能在创建时通过 `penpot_add_shapes` 设置。
- 形状删除:`penpot_delete_shapes` 通过 ID 从页面中删除一个或多个现有形状。
删除框架或组也会删除其子项,匹配 Penpot 自己的删除行为。
- 形状分组:`penpot_group_shapes` 将一个或多个兄弟形状包装到一个新组中(相当于 Ctrl+G)—
所有形状必须共享相同的父项。组在最高选定形状的 z-order 位置插入,
其边界框从子项的旋转感知 `selrect` 计算。`penpot_ungroup_shapes` 解散一个组(相当于 Ctrl+Shift+G)—
子项重新父级到组的父项在组的 z-order 槽,然后删除组。
- 形状复制:`penpot_clone_shapes` 复制一个或多个现有形状(以及,对于框架/组,其完整的后代子树),
每个形状都有新的 ID — 普通形状复制,如 Penpot 自己的 Ctrl+D,不是组件实例。可选的 `dx`/`dy` 偏移每个克隆
从其源(默认为无偏移,直接堆叠在原始形状之上);可选的 `parentId`/`frameId` 重新父级每个克隆的根,
而不是将其留在其源旁边。
- 形状堆叠顺序:`penpot_reorder_shapes` 在其兄弟中更改形状的 z-order,
匹配 Penpot 自己的“Bring to front” / “Send to back” / “Forward” / “Backward” UI 操作。
`action` 是 `front`/`back`(堆栈顶部/底部)、`forward`/`backward`(与下一个/上一个兄弟交换 —
如果已经在该端,则为 no-op),或 `before`/`after`(相对于另一个兄弟放置,给定 `targetId`)。
仅在现有兄弟之间重新排序 — 它不会重新父级。
- 对齐和分布:`penpot_align_shapes` 在一个公共边缘或中心对齐两个或多个形状(`left`/`right`/`top`/`bottom`/`center-h`/`center-v`),
而 `penpot_distribute_shapes` 传播三个或多个形状,以便它们之间的间隙相等(`horizontal`/`vertical`)—
匹配 Penpot 自己的单击对齐/分布操作,而不是智能体计算像素位置。两个操作都作用于每个形状的可见边界框(其 `selrect`),
因此旋转的形状按其渲染边界对齐;对齐永远不会移动组作为一个整体,而分布保持两个最外层形状固定。
对齐或分布框架/组会将其整个子项树一起移动相同的偏移量。
- 批量编辑:`penpot_batch` 将有序的创建/更新/删除/重新排序操作列表应用于单个 `update-file` 更改集 —
一个 `revn`/`vern` 往返旅程,无论触摸多少形状,而不是每个形状的一个 RPC 调用(以及随之而来的 `revn` 冲突)。
每个操作采用与匹配的独立工具相同的字段(`penpot_add_shapes` 用于 `create`,`penpot_update_shapes` 用于 `update` 等);
`create` 操作可以设置显式 `id` 并在后面的操作中引用为 `parentId`/`frameId` 或目标 `update`/`delete`/`reorder`,
因此可以在一次往返旅程中构建整个表单或卡片网格。
- 撤销点:`penpot_checkpoint` 捕获形状并返回一个 `checkpointId`;
`penpot_restore_checkpoint` 撤销自此以来的所有操作 — 包括错误的 `penpot_delete_shapes` 调用,
否则除了 Penpot 自己的 UI 外没有撤销路径。提供 `pageId` 以将快照范围限定为单个页面;
省略 `pageId` 以获取涵盖整个文件的全页快照。通过将当前状态与快照进行差异化,
并以单个 `update-file` 调用重放校正更改(重新创建缺少的内容、删除新增的内容、将更改的内容恢复到其快照字段),
因为 Penpot 的 RPC 没有“恢复到 revn X”的原始操作。快照存储在 MCP 服务器的内存中(不是 Penpot 文件,不是磁盘),
并且可重复使用 — 只要您愿意,就可以多次恢复到同一个点,直到通过 `penpot_discard_checkpoint` 或服务器进程重启显式释放。
- 形状查找:`penpot_get_shape` 通过 ID 获取单个形状,而不通过 `penpot_get_file_snapshot` 获取整个页面。
默认情况下,将形状的完整后代子树嵌套在 `shapes` 下;`includeDescendants` 和 `maxDepth` 控制返回的子树有多少。
始终包含一个 `componentInfo` 字段:`linkState`(`"linked"` / `"detached"` / `"not-an-instance"` / `"main-component-root"`),
以及 — 对于相同文件的链接实例 — 一个 `driftedFields` 数组,列出采用驼峰命名法的字段名称,这些名称与主组件的当前定义不同(为空 = 完全同步)。
- 形状搜索:`penpot_find_shapes` 在页面上搜索每个形状以匹配一个或多个谓词 — `type`、`name`(精确匹配)、`nameContains`(不区分大小写的子字符串)、
`textContains`(不区分大小写的子字符串针对文本形状的渲染字符)、`isComponentInstance` 和/或 `isRoot` — 所有给定的过滤器都必须匹配(AND)。
省略每个过滤器以列出整个页面。返回每个匹配项的 ID/类型/名称/位置/大小,以及 `linkState` 和(对于链接的相同文件实例)`driftedFields`;
对匹配项的 ID 使用 `penpot_get_shape` 以获取完整的子树和详细的 `componentInfo`,可选地限定为 `limit`。
- 文本测量:`penpot_measure_text` 为给定字体(系列/大小/权重)计算字符串的实际渲染宽度/高度,而不创建或触摸任何形状 —
消除了围绕 `width`/`height` 的猜测工作,当调用 `penpot_add_shapes`/`penpot_update_shapes` 进行文本操作时。
首先尝试 Google Fonts(按系列名称,无需 API 密钥);如果系列不存在,则搜索可访问的 Penpot 团队的所有 Penpot 实例以匹配自定义/团队字体,
因此可以直接上传到您的 Penpot 实例的字体也是可测量的。测量实际的字符间距(加上字间距),因此数字与 Penpot 渲染的数字匹配。
按显式 `\n` 分割;传递 `maxWidth` 以获取固定宽度框的换行符。
- 自动布局:`frame` 可以通过可选的 `layout` 字段声明 flex 或 grid 布局(方向、间隙、填充、对齐;grid 还接受行/列轨道定义)。
任何形状都可以设置 `layoutItem` 以控制其在自动布局父级内的放置(尺寸、对其、边距、最小/最大尺寸,以及 grid 父级的行/列/跨度)。
- 组件:`penpot_create_component` 将形状树(与 `penpot_add_shapes` 相同的规范)注册为组件的主实例;
给形状显式 `id` 以嵌套它们,并且不父级到兄弟的形状成为根。`penpot_add_component_instance` 然后将该树的一个完整副本放置在页面的其他位置,
通过 Penpot 的 `shape-ref` 链接回主实例,使其被识别为正确的组件副本 — 可重复使用任意次数。
- 变体:`penpot_create_variant_group` 将两个或多个组件(按与 `penpot_create_component` 相同的方式构建,每个变体一个)分组到一个容器中,
并带有共享的属性轴标签(例如“按钮”与 Type=Primary/Secondary)。这将启用 Penpot 实际的变体交换系统 —
通过 Penpot 插件 API 确认,包括 `instance.switchVariant(pos, value)` 正确交换放置的实例在变体之间。
`penpot_add_variant` 向已经存在的变体组容器中添加新变体:接受与 `penpot_create_variant_group` 的 `variants` 数组中的一个条目相同的形状规范,
以及由该工具返回的 `containerId`(或通过 `penpot_find_shapes` 查找),然后将新变体的主实例附加到容器的 `shapes` 数组中,
并注册一个新组件 — 所有操作在一个 `update-file` 调用中。
- `penpot_list_components` 枚举文件的现有组件(来自其 `data.components` 映射),
而不是要求调用者自己在同一会话中创建它们或手动解析 `penpot_get_file_snapshot` 的输出。
每个条目包括 `componentId`(可与 `penpot_add_component_instance` 一起使用)、`name`、`mainInstanceId`/`mainInstancePage`,
以及对于变体组件 — `variantId`/`variantProperties`。
- 媒体上传:`penpot_upload_media` 将图像或其他资产上传到 Penpot 文件,并返回媒体对象元数据(`id`、`width`、`height`、`mtype`)。
接受本地文件路径(`filePath`)、远程 URL(`url` — Penpot 服务器直接获取)或 base64 编码字节(`dataBase64` + `mtype`)。
返回的 `id` 用作创建 `image` 形状时的 `mediaId`。
- 评论:`penpot_list_comment_threads` 列出文件的所有评论线程;`penpot_get_comments` 获取线程内的回复;
`penpot_create_comment_thread` 将新线程固定到画布位置(x/y,可选 `frameId`);`penpot_create_comment` 添加回复;
`penpot_update_comment` 编辑评论的文本;`penpot_resolve_comment_thread` 将线程标记为已解决或重新打开;
`penpot_delete_comment` 删除单个回复;`penpot_delete_comment_thread` 删除整个线程。
- 版本历史:`penpot_list_file_snapshots` 列出文件的所有命名快照;`penpot_create_file_snapshot` 将当前状态保存为命名版本
(等效于 Penpot UI 中的“保存版本”,带有可选的 `label`);`penpot_restore_file_snapshot` 将实时文件回滚到快照(Penpot
自动在恢复前保存系统备份,因此恢复本身是可撤销的);`penpot_rename_file_snapshot` / `penpot_delete_file_snapshot` 管理
用户创建的版本;`penpot_lock_file_snapshot` / `penpot_unlock_file_snapshot` 保护版本免受意外删除;
`penpot_get_file_snapshot_data` 返回快照的完整文件内容以进行只读检查。
- 通过 `penpot_get_file_snapshot` 读取文件的当前形状树。
- 通过 `penpot_export_shape`(单个形状)和 `penpot_export_batch`(多个形状在一个调用中,按顺序返回,跨任意数量的页面 — 使用 `shapes` 数组进行多页面导出;
`shapeIds` 简写仍然可用,如果所有形状共享同一个页面)将形状或页面渲染为 PNG/SVG/PDF — 需要
`PENPOT_LOGIN_EMAIL`/`PENPOT_LOGIN_PASSWORD`、`PENPOT_OIDC_USERNAME`/`PENPOT_OIDC_PASSWORD` 或 `PENPOT_AUTH_TOKEN_COOKIE`,参见下文 —
您的端点不需要浏览器标签页或 Penpot 插件会话。
## 渲染/导出能力
`penpot_export_shape` 将单个形状(或整个页面,通过传递其根框架的 ID)渲染为 PNG、SVG 或 PDF,
使用 Penpot 自己的服务器端导出程序 — 与 Penpot 自托管堆栈已经运行的相同 headless-Chromium 支持的服务
(`penpotapp/exporter` 容器)。`penpot_export_batch` 接受 `shapeIds` 数组(同一 `pageId` 上的所有形状)或 `shapes`
数组,其中每个条目可以指定其自己的 `pageId`、`format`、`scale` 和 `name` —
启用整个文件的导出,跨多个页面在一个 API 调用中。支持所有格式(PNG/SVG/PDF)。此软件包中不发生浏览器自动化;
这是针对您的 Penpot 实例的两个 HTTP 调用(身份验证,然后导出)。
此操作需要 **第二个独立身份验证模式**,与此软件包的其余部分不同:
Penpot 的导出管道(`POST /api/export`)是一个独立的微服务,与 `/api/rpc/command/*` 接口在此使用的表面不同,
它纯粹通过会话 cookie 进行身份验证 — 个人访问令牌永远不会在该路径上读取(确认读取
Penpot 导出程序的源代码:其 `wrap-auth` 查找一个字面上的 `auth-token` cookie 而不是其他任何东西),
独立于 `PENPOT_ACCESS_TOKEN`。
配置以下三个选项之一:
**选项 A — 密码登录**(`PENPOT_LOGIN_EMAIL` + `PENPOT_LOGIN_PASSWORD`):
服务器使用电子邮件/密码登录以获取 `auth-token` cookie 并缓存它以供进程生命周期使用,
自动在到期时重新登录。适用于公开密码登录的实例。`PENPOT_ACCESS_TOKEN` 和这些凭据必须属于
**同一个 Penpot 帐户**(或至少具有访问同一个团队的帐户);不匹配的帐户会导致静默的
10 秒渲染超时,因为导出程序无法区分“拒绝访问”和“形状不可见”。
**选项 B — 无头 OIDC/SSO 登录**(`PENPOT_OIDC_USERNAME` + `PENPOT_OIDC_PASSWORD`):
服务器驱动 OIDC 重定向流程的纯 HTTP:它遵循 Penpot 的 OAuth 端点重定向链到身份提供者的登录页面,
解析 HTML 登录表单,提交凭据并捕获生成的 `auth-token` cookie — 无需浏览器或外部工具。
重新登录时会自动重新登录。适用于基于表单的 IdP 呈现标准 HTML 登录表单(Keycloak、Authentik、Dex 等)。
**不适用于** JavaScript 驱动的登录页面(Google、Microsoft、Okta托管登录)因为它们的登录 UI 是 SPA 外壳,没有服务器渲染的表单。
将 `PENPOT_OIDC_PROVIDER` 设置为 Penpot 实例上配置的 OAuth 提供者名称(默认为 `"oidc"`)。
大多数自托管设置都使用此默认值;如果登录不启动,请检查您的 Penpot 服务器标志。
**选项 C — 预先获取的 SSO/OIDC Cookie**(`PENPOT_AUTH_TOKEN_COOKIE`):
粘贴您已经完成 OIDC/SSO 登录的浏览器会话中的 `auth-token` Cookie 的原始值。
服务器直接使用它并发出明确的错误提示,如果它过期(因为它没有凭据重新登录)。
适用于密码登录禁用且 IdP 使用 JavaScript 驱动的登录页面(其中选项 B 无法访问)的实例。
要获取 Cookie 值:
1. 在浏览器中打开您的 Penpot 实例并完成 SSO/OIDC 登录。
2. 打开 DevTools → 应用程序 → Cookie → 您的 Penpot 域名。
3. 复制 `auth-token` Cookie 的值(不是整个 `Cookie:` 标头
— 只是 `auth-token=` 后的令牌值)。
4. 将 `PENPOT_AUTH_TOKEN_COOKIE` 设置为该值。
如果未配置任何选项,则 `penpot_export_shape` 不会注册,并且其他所有工具都像以前一样正常工作。
## 设置
1. 生成一个Penpot访问令牌:您的Penpot实例 → 账户设置 → 访问令牌。(自托管实例:如果后端/前端未使用`enable-access-tokens`启动,则此部分被隐藏。)
2. 为您的项目创建令牌文件(参见消费存储库中的`design-tokens/*.tokens.json`示例 - 或根据以下架构编写您自己的匹配文件)。
3. 如果您想要`penpot_export_shape`,还需配置以下之一:
- **密码登录:** 设置`PENPOT_LOGIN_EMAIL`/`PENPOT_LOGIN_PASSWORD`为**同一账户**,即上述访问令牌所属的账户。
- **无头OIDC登录:** 设置`PENPOT_OIDC_USERNAME`/`PENPOT_OIDC_PASSWORD`为您的IdP凭证。适用于基于表单的IdP(Keycloak,Authentik,Dex)。如果您的提供商名称与默认`"oidc"`不同,请设置`PENPOT_OIDC_PROVIDER`。
- **SSO/OIDC cookie:** 设置`PENPOT_AUTH_TOKEN_COOKIE`为原始`auth-token` cookie值,来自您已完成SSO登录的浏览器会话(参见上面的“渲染/导出功能”)。用于JavaScript驱动的IdP登录页面,无头OIDC无法访问。
如果您不需要渲染,可以完全跳过此步骤。
4. *(可选)* 要在服务器重启之间持久保存检查点,请设置`PENPOT_CHECKPOINTS_PATH`为本地目录路径。设置后,每个`penpot_checkpoint`调用都会将JSON文件写入该目录;启动时,服务器会重新加载已经存在的任何文件,以便检查点在MCP服务器重启期间存活。目录将自动创建。没有此设置,检查点仅存在于进程内存中(以前的行为)。
5. 在您的MCP客户端配置中注册此服务器,例如(密码登录变体):
```json
{
"mcpServers": {
"penpot-headless": {
"command": "npx",
"args": ["-y", "@ai-dala/penpot-headless"],
"env": {
"PENPOT_BASE_URL": "https://your-penpot-instance.example.com",
"PENPOT_ACCESS_TOKEN": "your-token-here",
"PENPOT_TOKENS_PATH": "/path/to/your-project/design-tokens/tokens.json",
"PENPOT_LOGIN_EMAIL": "same-account-as-the-token-above@example.com",
"PENPOT_LOGIN_PASSWORD": "that-account's-password"
}
}
}
}
```
或者,对于仅限SSO/OIDC且具有基于表单的IdP(无头OIDC登录)的实例:
```json
{
"mcpServers": {
"penpot-headless": {
"command": "npx",
"args": ["-y", "@ai-dala/penpot-headless"],
"env": {
"PENPOT_BASE_URL": "https://your-penpot-instance.example.com",
"PENPOT_ACCESS_TOKEN": "your-token-here",
"PENPOT_TOKENS_PATH": "/path/to/your-project/design-tokens/tokens.json",
"PENPOT_OIDC_USERNAME": "your-idp-username-or-email",
"PENPOT_OIDC_PASSWORD": "your-idp-password"
}
}
}
}
```
或者,对于仅限SSO/OIDC且具有JavaScript驱动的IdP登录页面的实例:
```json
{
"mcpServers": {
"penpot-headless": {
"command": "npx",
"args": ["-y", "@ai-dala/penpot-headless"],
"env": {
"PENPOT_BASE_URL": "https://your-penpot-instance.example.com",
"PENPOT_ACCESS_TOKEN": "your-token-here",
"PENPOT_TOKENS_PATH": "/path/to/your-project/design-tokens/tokens.json",
"PENPOT_AUTH_TOKEN_COOKIE": "the-auth-token-cookie-value-from-your-browser"
}
}
}
}
```
或者,如果从本地克隆而不是发布包工作:
```json
{
"mcpServers": {
"penpot-headless": {
"command": "npx",
"args": ["tsx", "/path/to/penpot-mcp/src/server.ts"],
"env": {
"PENPOT_BASE_URL": "https://your-penpot-instance.example.com",
"PENPOT_ACCESS_TOKEN": "your-token-here",
"PENPOT_TOKENS_PATH": "/path/to/your-project/design-tokens/tokens.json",
"PENPOT_LOGIN_EMAIL": "same-account-as-the-token-above@example.com",
"PENPOT_LOGIN_PASSWORD": "that-account's-password"
}
}
}
}
```
## 令牌文件架构
```json
{
"colors": {
"accent": "#7AA2FF",
"bg-surface": "#111726"
},
"fonts": {
"sans": { "family": "Inter", "weights": ["400", "500", "600"] }
},
"spacing": {
"sm": 8,
"md": 16,
"lg": 24
},
"radii": {
"sm": 4,
"md": 8,
"pill": 999
},
"shadows": {
"card": {
"style": "drop-shadow",
"color": "#000000",
"opacity": 0.3,
"offsetX": 4,
"offsetY": 4,
"blur": 8,
"spread": 2
}
}
}
```
所有四个令牌表都是可选的,除了`colors`。`penpot_add_shapes`/`penpot_update_shapes`中的任何颜色类型字段都接受字面十六进制字符串或`{ "token": "accent" }`,在调用时根据此文件解析。同样适用于:
- **数字字段**(布局间隙/填充,布局项边距/最小/最大尺寸和圆角`r1`-`r4`)- 字面数字或`{ "token": "sm" }`,根据`spacing`(间隙/填充/边距)或`radii`(圆角)解析。
- **形状上的`shadows`** - 数组,其中每个条目是内联`{ style, color, opacity, offsetX, offsetY, blur, spread }`对象(颜色本身也可能是`{ "token": "name" }`引用)或`{ "token": "card" }`,根据`shadows`解析。`style`是`"drop-shadow"`(默认)或`"inner-shadow"`。
## 测试
两个测试套件,因所需条件大不相同而分开:
- **单元测试** (`npm test`) - `shape-builders.ts`中的纯函数(旋转矩阵数学,布局属性映射,`penpot_update_shapes`依赖的 camelCase-in/kebab-case-out 形状提取)和`font-metrics.ts`(分行/换行逻辑,针对手工构建的假字体进行测量,因此无需网络调用)。无需网络,无需Penpot账户,在任何地方运行。此测试套件在每次推送/拉取请求时由CI运行。
- **集成测试** (`npm run test:integration`) - 通过`PENPOT_BASE_URL`/`PENPOT_ACCESS_TOKEN`使用真正的Penpot实例测试实际的MCP工具处理程序(读取`.env`,与服务器本身相同)。每个测试都创建自己的临时项目,并在`finally`块中删除,即使失败 - 参见`test/integration/helpers/scratch-project.ts`。如果凭证未配置,则跳过自身(不会失败)**仅限本地/手动运行 - 故意不在CI中运行**,因为它会变异真正的Penpot账户,而CI凭证超出此项目范围。在信任对Penpot线框图(旋转,布局,组件,变体)的更改之前,请自行运行。`font-metrics.test.ts`也存在于此套件中,尽管不需要Penpot凭证 - 它对公共Google Fonts CDN进行实际调用,如果网络调用失败,则跳过自身,而不是需要`PENPOT_ACCESS_TOKEN`。
此分割很重要,因为Penpot接受更改的RPC架构并不等同于Penpot编辑器正确渲染或识别它 - `shape-builders.ts`中记录的变体ID错误(`variantContainerAttrs`)仅通过实时测试捕获,而非架构验证成功。`npm run test:all`运行两个测试套件。
## 复制到另一个项目
此包作为`@ai-dala/penpot-headless`发布到npm,因此大多数使用项目只需通过`npx`引用它(参见上文),无需本地复制。
如果您宁愿将其作为供应商,此目录有自己的`package.json`/`tsconfig.json`,仅依赖于`@modelcontextprotocol/sdk`,`zod`和`opentype.js`(由`penpot_measure_text`使用以解析真实字体文件)- 无框架依赖。将整个`penpot-headless/`目录复制到新项目,运行`npm install`,为该项目编写令牌文件,并使用指向它的`PENPOT_TOKENS_PATH`注册服务器。
## 已知限制
- `penpot_measure_text`不应用连字替换(例如渲染“fi”为一个字形)- 它汇总单个字形进度(带有真实GPOS字距调整)而不是使用opentype.js的内置形状,因为该形状路径在大多数当前生成的Google Fonts上都会抛出异常,包括Inter和Roboto(参见`font-metrics.ts`中的`measureLineWidth`)。这是故意权衡,而不是开放的差距:跳过连字的宽度差异对于典型的UI文本而言最多为几个百分点,而使用内置形状会导致大多数真实世界字体的硬崩溃。
- `penpot_measure_text`通过家族名称(不区分大小写)和权重匹配Penpot自定义/团队字体。如果字体变体以多个样式(正常/斜体)存在相同的权重,则优先使用“正常”样式;如果您的用例需要为测量而使用特定的非正常样式,则此功能尚不可通过工具的输入参数控制。
## 可能的未来新增功能
以下是对可能降低代理摩擦的想法的优先级排序:
- [x] `penpot_update_shapes` — 在原位编辑现有形状的几何/填充/描边/文本。(已完成 — 见上文。)
- [x] `penpot_delete_shapes` — 封装 `del-obj`。(已完成 — 见上文。)
- [x] `penpot_get_shape` — 按 ID查找一个形状(或一个小的子树),而不是通过 `penpot_get_file_snapshot` 拉取整个页面并在客户端解析一个可能很大的 JSON 对象来查找它。(已完成 — 见上文。)
- [x] `penpot_measure_text` — `penpot_add_shapes` 中的 `width`/`height` 只是调用者必须猜测的数字;Penpot 只在布局后知道实际渲染边界。(已完成 — 见上文。)
- [x] `penpot_clone_shapes` — 简单形状复制(不是通过 `penpot_add_component_instance` 已经涵盖的组件实例路径)。(已完成 — 见上文。)
- [x] `penpot_reorder_shapes` — 通过 ID 将形状带到前面/发送到后面/向前/向后/之前/之后。(已完成 — 见上文。)
- [x] `penpot_list_components` — 枚举一个文件的现有组件,而不是要求调用者自己在同一会话中创建它们或手动解析快照的 `components` 映射。(已完成 — 见上文。)
- [x] `penpot_find_shapes` — 基于谓词/名称的页面搜索(镜像插件 API 的 `findShapes`),而不是调用者自己遍历树以查找特定形状。(已完成 — 见上文。)
- [x] `penpot_batch` — 将创建/更新/删除/重新排序操作的有序列表应用为单个 `update-file` 更改集。(已完成 — 见上文。)
- [x] `penpot_checkpoint` / `penpot_restore_checkpoint` / `penpot_discard_checkpoint` — 在进行风险较高的多步骤编辑之前快照形状,并在之后恢复它们。(已完成 — 见上文。)
- [x] `penpot_align_shapes` / `penpot_distribute_shapes` — Penpot 自有 UI 的一键对齐-左/中心/均匀分布操作,而此软件包中没有这些操作。(已完成 — 见上文。)
- [x] 支持 Token 文件中的间距/半径/阴影,而不仅仅是 `colors` 和 `fonts`。(已完成 — 见上文/Token 文件架构。)
- [x] 在 `penpot_add_shapes` 中添加路径/椭圆形状和布尔运算(联合/减/交/异或)。(已完成 — 见上文。)
- [x] 扩展 `penpot_update_shapes` 以涵盖路径/组/圆圈/svg-raw/image/布尔值(以及其他形状类型)。(已完成 — 见上文。)
- [x] 为 `penpot_measure_text` 添加 Penpot 团队/自定义字体查找 — 如果请求的字体系列在 Google Fonts 上找不到,则搜索所有可访问的 Penpot 团队的匹配自定义/团队字体变体。(已完成 — 见上文。)
- [x] 向已存在的组件/组添加变体。(已完成 — 见上文。)
- [x] `penpot_list_pages` / `penpot_rename_page` / `penpot_delete_page` — 存在 `penpot_create_page`,现在其他页面 CRUD 操作也已完成。(已完成 — 见上文。)
- [x] 通过 `PENPOT_AUTH_TOKEN_COOKIE` 支持仅限 SSO/OIDC 实例的 `penpot_export_shape`。(已完成 — 见上文。)
- [x] `rpc-client.ts`、`transit.ts` 和 `tools/project-files.ts` 的单元测试覆盖 — 目前只有零单元测试,只能通过集成套件访问,不包括在 CI 中。(已完成 — 见上文。)
- [x] 渐变和图像填充 — `Fill`/`Stroke` 在 `shape-builders.ts` 中仅携带一个扁平的 `color`/`opacity`;Penpot 支持线性/径向渐变填充和图像填充。(已完成 — 见上文。)
- [x] 图像形状/资产上传 — 没有 `image` 形状构建器,也没有上传媒体的 RPC 调用。(已完成 — 见上文。)
- [x] 不透明度/隐藏/锁定/混合模式标志。(已完成 — 见上文。)
- [x] 丰富的每范围文本格式化 — `text()` 在 `shape-builders.ts` 中始终发出一个段落和一个样式运行;没有每个字符范围的样式、文本对齐、行高、字母间距或文本装饰。(已完成 — 见上文。)
- [x] 作为一流工具的分组/取消分组 — `group()` 已经存在作为内部构建器,但没有通过 MCP 工具公开分组现有形状或取消分组。(已完成 — 见上文。)
- [x] 调整约束 — Penpot 的 `constraints-h`/`constraints-v`(形状在父级调整大小时如何表现)在 `EditableShapeFields` 或更新补丁架构中从未设置或公开。(已完成 — 见上文。)
以下是翻译后的文档:
- [x] 批量/整页导出 — `penpot_export_shape` 每次调用只接受一个 `shapeId`,不支持多形状或多页批量导出,并且只支持 `png`/`svg`(不支持 PDF)。(已完成 — `penpot_export_batch` 接受一个 `shapeIds` 数组(同一个 `pageId`,任意混合的 `format`/`scale`)或一个 `shapes` 数组,其中每个条目可以携带自己的 `pageId`、`format`、`scale` 和 `name` — 启用整个文件的跨多页导出,只需一次 `POST /api/export` 调用。通过 `matchAll` 在 transit+json 响应中解析每个结果的 `~:uri`/`~:mtype`/`~:filename`;资产下载是并行的。两个工具还接受 `pdf` 作为格式值。多页导出以前依赖于 MCP 架构,而不是导出客户端,客户端已经发送了每个规范的 `pageId`。)
- [x] 共享库组件 — `penpot_list_components` 和 `penpot_add_component_instance` 只查看当前文件自己的 `components` 映射;不支持从单独的连接的共享库文件中提取组件。(已完成 — `penpot_list_components` 现在接受 `includeLibraries: true`,它调用 `get-file-libraries` 来枚举所有链接的库文件(直接和传递)并调用每个文件的 `get-file` 来读取其 `components` 映射;每个库组件条目携带一个 `libraryFileId` 和 `libraryFileName` 字段在响应中。`penpot_add_component_instance` 现在接受一个可选的 `libraryFileId`;当提供时,组件的主实例树在该库文件中查找,而不是当前文件,克隆实例的根 `component-file` 字段设置为库文件的 ID(与 Penpot 自己的编辑器为跨文件组件实例编写的内容相匹配)。注意:`get-file-libraries` 只返回文件元数据 — 它不包括形状/组件数据 — 因此每个库的单独 `get-file` 调用是必要的来枚举组件;这是 Penpot 自己的前端使用的相同两步模式。)
- [x] 评论 API — `penpot_list_comment_threads` 列出一个文件的所有线程;`penpot_get_comments` 获取线程内的回复;`penpot_create_comment_thread` 将新线程固定到画布位置(x/y + 可选 `frameId`);`penpot_create_comment` 添加回复;`penpot_update_comment` 编辑现有评论的文本;`penpot_resolve_comment_thread` 将线程标记为已解决或重新打开;`penpot_delete_comment` 删除单个回复;`penpot_delete_comment_thread` 删除整个线程及其所有回复。所有八个工具都通过 `PenpotRpcClient` 方法发送 kebab-case JSON 参数到 `get-comment-threads` / `create-comment-thread` / `update-comment-thread` / `delete-comment` / 等 RPC 端点(与所有其他工具相同的调用模式)。线格式已针对 Penpot 前端源代码 (`data/comments.cljs`) 进行验证:`position` 是一个嵌套的 `{ x, y }` JSON 对象;`frame-id` 在创建时是可选的(省略以放置在页面根目录);`is-resolved` 控制线程解决状态。)
- [x] 跨文件的文本搜索和替换 — `penpot_replace_text` 查找页面上的每个文本形状,其运行包含一个字面搜索字符串,并重写所有出现,单次 `update-file` 调用。匹配默认情况下不区分大小写(`caseSensitive: true` 来覆盖);可选的 `limit` 限制触摸的形状数量;空 `replacement` 删除匹配。替换按文本运行(叶节点)进行;搜索字符串跨越两个相邻运行在段落内不被匹配 — 这涵盖了所有文本通常在一个运行内的常见情况。(已完成 — 搜索字符串中的正则表达式元字符被转义,因此 `.` / `*` / `(` 等始终按字面匹配,而不是作为模式运算符。)
- [x] 跨页/整个文件的撤销 — `penpot_checkpoint` 现在接受一个可选的 `pageId`;省略它以快照文件中的每个页面,单次调用。`penpot_restore_checkpoint` 在单次 `update-file` 调用中恢复所有快照的页面。检查点仍然存在于服务器内存中,不会在重启后存活;需要该功能的调用者应该通过 `penpot_get_file_snapshot` 自己快照。(已完成 — `Checkpoint` 在 `checkpoints.ts` 中现在保存一个 `pages` 映射(pageId → 对象)而不是单个 `pageId`/`objects` 对;恢复循环遍历每个条目并累积所有更改,然后发送一个 `update-file` 调用 — 与 `penpot_batch` 使用的相同单次往返模式。)
- [x] 完全无头 OIDC/SSO 登录 — 驱动重定向流程而无需浏览器,而是要求 `PENPOT_AUTH_TOKEN_COOKIE` 必须手动获取。(已完成 — `PENPOT_OIDC_USERNAME` + `PENPOT_OIDC_PASSWORD` 触发导出客户端中的新 `'oidc'` 身份验证模式。服务器遵循 Penpot 的 `/api/auth/oauth/{provider}` 重定向链到身份提供者的登录页面,通过纯 HTTP(Node.js `http`/`https` 模块,无浏览器依赖),启发式地解析 HTML 登录表单以识别用户名/电子邮件和密码字段(包括隐藏的 CSRF/状态字段,这些字段被保留并逐字重新提交),并捕获生成的 `auth-token` cookie。多步骤流程 — 其中 IdP 在第 1 页上显示用户名,在第 2 页上显示密码(Authentik 样式)— 由一个 `MAX_FORM_STEPS` 循环处理,该循环重新解析和重新提交,直到出现 cookie。307/308 重定向保留 POST 方法+正文;301/302/303 根据 RFC 7231 切换到 GET。cookie jar 正确捕获 Penpot 自己在初始重定向上设置的 state/CSRF cookie,以便将其转发到 IdP 的回调。自动在 401/403 上重新登录(与密码模式相同)。`PENPOT_OIDC_PROVIDER` 控制提供者名称(默认为 `"oidc"`)。对于 JavaScript 驱动的 IdP 登录页面不适用 — 使用 `PENPOT_AUTH_TOKEN_COOKIE`。通过注入的模拟获取器进行单元测试;集成测试需要一个实时 OIDC 提供者,不在 CI 中。)
- [x] 服务器重启后检查点持久性 — 检查点(`penpot_checkpoint`/`penpot_restore_checkpoint`)当前仅存在于 MCP 服务器自己的内存中,并在重启时丢失。将它们持久化到磁盘(或让调用者提供一个检查点 ID 来保存/加载)将允许一个正在运行的多步骤编辑在中途通过服务器重启而存活。(已完成 — 设置 `PENPOT_CHECKPOINTS_PATH` 到一个本地目录;服务器将每个检查点写入该目录中的 `<uuid>.json`,并在启动时从中重新加载所有文件。`initCheckpointStore(dir)` 在 `server.ts` 主函数中调用一次;`saveCheckpoint` 现在是异步的,并等待 `writeFile`,因此调用者可以立即知道写入是否失败;`deleteCheckpoint` 也是异步的,并删除磁盘上的相应文件。当环境变量未设置时,内存行为不会改变。)
- [x] 单次调用多页批量导出 — `penpot_export_batch` 导出一个页面上的多个形状,单次调用;导出跨页面仍然需要每个 `pageId` 调用一次。如果批量导出成为整个文件导出的瓶颈,则值得重新审视。(已完成 — `penpot_export_batch` 现在接受 `shapeIds`(单页简写,向后兼容)或一个 `shapes` 数组,其中每个条目携带自己的 `pageId`、`format`、`scale` 和可选的 `name`。导出客户端已经在单个 `POST /api/export` 中发送了每个规范的 `pageId`;唯一的更改是扩展 MCP 工具的 Zod 架构以公开此功能。跨字段验证(至少一个 `shapeIds`/`shapes`;两者不可同时使用;`pageId` 需要 `shapeIds`;每个 `shapes` 条目都有一个 `pageId`,无论是每个条目还是通过顶级默认值)使用 `.refine()` 在单独的 `exportBatchInput` 架构上完成,而原始 `ZodObject` 被传递给 MCP SDK 的 `inputSchema` 以保留 `.shape`。)
- [x] 通过 `revn` 检测陈旧写入 — 每个写入工具(`penpot_update_shapes`/`penpot_batch`/`penpot_restore_checkpoint`/等)当前发送 `update-file` 盲目地,不会先检查文件的当前 `revn`(修订号)。如果人类在 Penpot 编辑器中打开文件并与代理会话并发编辑,则无论哪个写入落地,都会静默覆盖另一个而不会产生错误。Penpot 自己的 `update-file` RPC 需要一个 `revn`,应该拒绝一个建立在过时 `revn` 的更改集;目前这里没有任何内容读取或检查它。(已完成 — `PenpotStaleWriteError` 由 `updateFile` 在 `rpc-client.ts` 中抛出,只要 `update-file` 响应包含一个非空的 `lagged` 数组,意味着 Penpot 在我们的更改集之前应用了来自另一个会话的更改集。Penpot 的协作引擎仍然在上面应用我们的写入,因此写入在 HTTP 级别上成功,但错误会显示并发编辑情况,并带有明确的消息,指出新的 `revn`,并告诉调用者通过 `penpot_get_file_snapshot` 重新获取然后重试。适用于所有写入工具自动 — `penpot_add_shapes`、`penpot_update_shapes`、`penpot_delete_shapes`、`penpot_batch`、`penpot_restore_checkpoint`、`penpot_group_shapes`、`penpot_ungroup_shapes`、`penpot_clone_shapes`、`penpot_reorder_shapes`、`penpot_align_shapes`、`penpot_distribute_shapes`、页面 CRUD 等 — 因为它们都经过 `updateFile`。`PenpotStaleWriteError` 携带 `laggedCount`(并发更改集的数量)和 `result`(完整的 `update-file` 响应,包括新的 `revn`),因此需要新修订的调用者可以从错误中读取它,而不是需要额外的往返。)
- [x] 版本历史/分支支持 — `penpot_list_file_snapshots` 列出文件的所有命名快照(用户创建的版本和系统自动备份);`penpot_create_file_snapshot` 保存当前状态为命名版本(可选 `label`;Penpot 在省略时生成时间戳标签);`penpot_restore_file_snapshot` 将实时文件回滚到任何列出的快照(Penpot 自动在应用还原之前创建当前状态的系统备份,因此还原本身是可撤销的,通过还原最近的系统条目);`penpot_rename_file_snapshot` 重命名用户创建的快照;`penpot_delete_file_snapshot` 删除一个;`penpot_lock_file_snapshot` / `penpot_unlock_file_snapshot` 固定/取消固定快照以防止意外删除(只有快照的创建者可以锁定/解锁,并且只有用户创建的快照支持锁定 — 系统备份会自动过期)。`penpot_get_file_snapshot_data` 返回特定快照的完整文件内容,用于只读检查或比较,而不触摸实时文件。线格式已针对 `backend/src/app/rpc/commands/files_snapshot.clj` 和 `backend/src/app/features/file_snapshots.clj` 进行验证。
- [x] 组件实例漂移/覆盖可见性 — Penpot 的组成模型是令牌 → 元素 → 组件(带有变体)→ 由组件实例构建的屏幕,今天读取形状状态的工具(`penpot_get_shape`、`penpot_find_shapes`)不显示形状在该层次结构中的位置。组件实例的 `shape-ref` 链接到其主组件(以及是否漂移 — 自放置以来覆盖的字段,或完全分离)是不可见的:通过 `penpot_update_shapes` 编辑实例的代理无法当前分辨“此编辑创建实例级覆盖”或“此编辑更改原始形状”,这对于保持实例与组件的同步非常重要,而不是像临时 UI 编辑那样静默分离。范围:扩展 `penpot_get_shape`/`penpot_find_shapes` 以报告链接状态(`linked` / `detached` / 不是实例)和链接时,哪些字段与主组件的当前定义不同 — 只需只读可见性;显式分离/重新链接工具和发现优先的工具指导(在创建复制现有组件的形状之前检查 `penpot_list_components`)是可能的自然后续步骤,一旦漂移至少可见。(已完成 — `penpot_get_shape` 和 `penpot_find_shapes` 现在在其输出中包含组件链接状态。`penpot_get_shape` 添加一个顶级 `componentInfo` 字段,包含 `linkState`(`"linked"` / `"detached"` / `"not-an-instance"` / `"main-component-root"`)、`componentId` 和 `componentFileId`(如果存在)、`mainInstanceId` / `mainInstancePage` 用于相同文件的链接实例,以及一个 `driftedFields` 数组(驼峰式字段名称),列出此实例与主组件的当前定义不同的视觉属性 — 空数组表示完全同步。`penpot_find_shapes` 为每个匹配项添加 `linkState` 和 `componentId`,以及相同文件的链接实例的 `driftedFields`。漂移根据以下内容进行比较:`name`、`fills`、`strokes`、`shadows`、`opacity`、`hidden`、`blendMode`、`width`、`height`、`constraintsH`、`constraintsV` 和 `content`(文本形状);由于每个放置的实例根据设计生活在不同的画布位置,因此位置 `x`/`y` 被排除在外。库组件(`componentFile` ≠ 当前文件)报告 `linkState: "linked"` 但省略 `driftedFields`,因为库文件的页面需要额外的 RPC 调用 — 使用 `penpot_get_shape` 在库实例根目录上,然后如果需要跨文件漂移,请手动跟随其 `mainInstanceId`/`mainInstancePage`。)
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器