Penpot MCP types-generator 全解析:如何从插件 API 文档自动生成 api_types.yml
【免费下载链接】penpotPenpot: The open-source design platform for Product teams that need scalable collaboration.项目地址: https://gitcode.com/GitHub_Trending/pe/penpot
导读
mcp/types-generator是 Penpot MCP 服务器开发链路中的一组辅助脚本:它负责抓取 Penpot 插件(Plugin)API 的类型文档站点(TypeDoc 生成的静态页面),并将其清洗、汇总为一个结构化的api_types.yml文件。该 YAML 随后会被 MCP 服务器中的ApiDocs组件按需加载,作为"按需提供给 LLM 的 API 文档"数据源。阅读本文后,你将掌握整个类型的生成流程、两种数据来源(远程生产文档 vs 仓库内本地文档)、核心清理规则,以及 pixi/Caddy 等工具链的完整用法。
一、子项目定位:服务于 MCP 服务器的"文档工厂"
从 mcp/types-generator/README.md 的定义看,本目录是 Penpot MCP 服务器开发中的辅助脚本集,专门负责生成一份包含 Penpot 插件 API 类型及其文档说明的 YAML 文件。它自身不提供任何 MCP 能力,而是为 MCP 服务器供给数据资产。
从目录结构看,该子项目仅由 4 个文件组成(见 mcp/types-generator):
| 文件 | 作用 |
|---|---|
prepare_api_docs.py | 核心抓取与清洗脚本(Python) |
pixi.toml/pixi.lock | pixi 环境声明与锁文件,声明 Python 依赖 |
build | 一键构建脚本:先pixi install再执行脚本 |
README.md | 使用说明 |
而产出的数据会落入 mcp/packages/server/data/api_types.yml(仓库当前已提交,约 23000 行),被 MCP 服务器消费。在 ApiDocs.ts 中,该文件在运行时被解析加载,并通过PenpotApiInfoTool(见 PenpotApiInfoTool.ts)暴露给 LLM 按需查询。也就是说:本文讨论的是这份 "LLM 可查询的 API 类型字典" 是如何被生产出来的。
二、环境搭建:基于 pixi 的依赖管理
项目使用 pixi 管理 Python 运行环境(该工具已包含在 Penpot 的 devenv 中),因此无需手工创建 virtualenv。执行环境安装:
pixi install按 README 说明,此步骤是可选的——后面的
build脚本会代为执行pixi install。
依赖清单定义在 pixi.toml 中,从源码可以确认实际用到的核心库与版本约束:
[dependencies] python = "3.11.*" beautifulsoup4 = ">=4.13.5,<5" markdownify = ">=1.1.0,<2" requests = ">=2.32.5,<3" "ruamel.yaml" = ">=0.18.15,<0.19" [pypi-dependencies] sensai-utils = ">=1.5.0, <2"这些依赖与 prepare_api_docs.py 的 import 一一对应:
requests:抓取远程 HTML 页面(_fetch方法中调用requests.get);beautifulsoup4:解析 TypeDoc 生成的 HTML DOM,定位.col-content、.tsd-signature、.tsd-member等节点;markdownify:把清洗后的 HTML 片段转成 Markdown(子类化MarkdownConverter定制转换行为);ruamel.yaml:以 YAML 块状字面量(block literal)风格写出结果,保留|-语义以保持长文档可读;sensai-utils:提供logging.run_main包装入口与日志器。
另外值得注意pixi.toml中声明了platforms = ["win-64","linux-64"],即该工具链在 Windows 与 Linux 下均可安装使用。
三、直接抓取线上文档:prepare_api_docs.py <url>
3.1 脚本行为
prepare_api_docs.py从给定的 Web URL 读取 Penpot 插件 API 文档,将其汇总进单个 YAML 文件。成功执行后会在父级目录的packages/server/data下生成api_types.yml。脚本主入口(见 prepare_api_docs.py 中main()函数)把目标目录固定解析为:
target_dir = Path(__file__).parent.parent / "packages" / "server" / "data"即无论从哪个目录调用,输出总是落到 mcp/packages/server/data/api_types.yml。
脚本还预置了两个常量(对应 README 中的两条路径):
LOCAL_API_DOCS_URL = "http://localhost:9090" PROD_API_DOCS_URL = "https://doc.plugins.penpot.app" DEFAULT_API_DOCS_URL = LOCAL_API_DOCS_URL # 不传 URL 时默认抓本地3.2 运行方式
手动运行(需已执行过pixi install):
pixi run python prepare_api_docs.py <url>也可以直接调用封装脚本 build,它会额外自动完成 pixi 环境安装(pixi install+pixi run python prepare_api_docs.py $URL),默认 URL 同样是http://localhost:9090:
./build <url>例如,基于当前生产环境的 Penpot 插件 API 文档生成(即 README 示例):
./build https://doc.plugins.penpot.app3.3 输出文件形态
生成的api_types.yml以类型名 →TypeInfo的组织方式存放。TypeInfo数据类(见源码第 106–123 行)包含两个字段:
overview:类型主文档,包含全部声明/签名但不含成员详情;members:按成员分组(如 "Properties"、"Methods")映射到"成员名 → Markdown 描述"。
此外每个类型会在overview末尾追加一段 "Referenced by: ...",列出所有引用它的其他类型——这是脚本在抓取每个页面时,通过解析.tsd-signature内a.tsd-signature-type链接收集到的反向引用关系(见process_page与add_referencing_types)。仓库中已生成的样例(节选)如下:
Penpot: overview: |- Interface Penpot ================ These are methods and properties available on the `penpot` global object. ``` interface Penpot { ui: { open: ( name: string, url: string, options?: { width: number; height: number; hidden?: boolean }, ) => void; ... ``` ## 四、基于仓库内最新文档生成:`pnpm run build:types` ### 4.1 前提:需要 Caddy 这种模式下数据源是**仓库内当前源码**实时构建出的文档页面,因此必须先起一个静态文件服务器。README 明确要求 Caddy 已安装并在系统 PATH 中(Caddy 同时被下文 `build:types` 脚本内部调用)。 ### 4.2 一键流程 在 [mcp](https://link.gitcode.com/i/61b88d5bac6dc6c6136964e2ec89c22e) 目录(`types-generator` 的父级)执行: ```bash pnpm run build:types该命令对应 mcp/package.json 中的脚本定义"build:types": "bash ./scripts/build-types",实际执行 build-types:
# 1. 默认 URL 指向本地 9090,因此先构建本地文档 if [[ "$URL" = "http://localhost:9090" ]]; then pushd ../../plugins pnpm install pnpm run build:doc popd fi # 2. 用 Caddy 起 9090 静态服务,并同时执行抓取脚本 pnpx concurrently --kill-others-on-fail -s last -k \ "caddy file-server --root ../../plugins/dist/doc/ --listen :9090" \ "bash ../types-generator/build $URL"结合 plugins/package.json 中"build:doc": "typedoc ... --out dist/doc"可知完整链路为:
- 在 plugins 目录执行
pnpm run build:doc,用 TypeDoc 把插件运行时类型定义生成静态站点到plugins/dist/doc/; caddy file-server将该目录以127.0.0.1:9090暴露(build:types的监听地址为:9090,README 中单独起服务示例为127.0.0.1:9090);../types-generator/build(即前一节的封装脚本)随后调用prepare_api_docs.py http://localhost:9090完成抓取与转换。
由于concurrently配合--kill-others-on-fail -s last -k,只要抓取脚本失败,Caddy 进程也会被一并终止,避免遗留后台服务。
4.3 仅启动文档服务器
若只想在本地查看文档而暂不执行抓取,可跳过脚本、只启动静态服务器(在mcp目录下):
caddy file-server --root ../plugins/dist/doc/ --listen 127.0.0.1:9090对应仓库路径即 plugins/dist/doc(需先构建产生)。同时build-types脚本也支持通过环境变量PENPOT_PLUGINS_API_DOC_URL覆盖目标 URL,例如把它指向上游文档地址即可跳过本地构建、直接抓取远程文档。
五、深入:脚本内部如何把 HTML 清洗成"LLM 友好"的 Markdown
prepare_api_docs.py最见功力的部分在于对 TypeDoc 页面做了大量定制清洗,这正是保证最终 YAML 体积可控、语义干净的关键。理解这些规则有助于你判断何时需要重新生成、以及生成结果的形态。
5.1 页面定位与成员拆分
PenpotAPIDocsProcessor.run()先抓取modules.html,在.col-content中收集所有指向interfaces/与types/的链接作为待处理类型列表,再逐页处理。处理单个类型页时(process_page):
- 遍历
.col-content下带tsd-member-group的分组标签,按h2文本(如 Properties / Methods)归类,把组内每个tsd-member拆成"成员名 → Markdown 描述"; - 成员名的提取优先取
a.tsd-anchor的id,否则取h3 > span的文本(无法确定时会触发断言); - 从
h3标题中提取的tsd-tag(如Readonly)会被重新插回签名区,标题本身随后被移除(因为它是冗余信息); - 成员分组从 DOM 中删除后,剩余内容作为该类型的
overview。
5.2 定制 Markdown 转换规则
PenpotAPIContentMarkdownConverter继承自 markdownify,通过重写process_tag实现了一套针对 Penpot API 文档的规则,主要可归纳为:
- 丢弃导航噪音:面包屑(
tsd-breadcrumb)、索引区(tsd-index-content)、按钮(如 "Copy")、"Defined in" 列表项、"Inherited from" 段落一律置空; - 代码块化签名:
tsd-signature中的<br>转成换行,仅保留readonly标记(optional因已通过?表达而删除),整体包裹进```代码块;<pre>块也会去掉内部按钮后转成代码块; - 去冗余列表:仅含单个
<li>的tsd-signatures列表会被"拆包"为普通<div>递归处理,避免出现只有一条且带缩进的项目符号; - 链接取纯文本:
<a>链接只保留文字,减少无关超链接对 LLM 上下文的干扰。
5.3 YAML 写出:全程块状字面量
YamlConverter使用ruamel.yaml并开启preserve_quotes、把宽度放宽到 4096 以阻止自动折行,再将所有字符串统一递归转换为LiteralScalarString,使生成的 YAML 采用|-块状风格。这样长 Markdown 描述不会被折叠成冗长单行,便于人读与增量 diff。
5.4 调试辅助函数
源码中还带有一个debug_type_conversion函数(仅用于调试,默认被注释),可单独处理某个类型页并把转换结果打印到控制台,例如将注释行替换为:
debug_type_conversion("interfaces/Path.html", LOCAL_API_DOCS_URL)在扩展转换规则、排查某个类型为何转换异常时非常有用。
六、产出如何被消费:从 api_types.yml 到 MCP 工具
为了让"生成 YAML"这件事的上下文更完整,这里补充其在运行时一侧的消费链路(均位于 mcp/packages/server/src):
- ApiDocs.ts:
ApiDocs类在初始化时从data/api_types.yml加载全部类型数据(运行时默认路径为当前工作目录下的data/api_types.yml); - PenpotApiInfoTool.ts:作为 MCP 工具暴露 API 文档查询能力,其构造器接收
ApiDocs实例; - PenpotMcpServer.ts:服务器启动时实例化
ApiDocs并注入各工具。
也就是说,当你修改了插件 API 类型或文档注释后,应当重新执行本 README 描述的类型生成流程(推荐走pnpm run build:types的本地模式),使api_types.yml与最新源码保持同步,MCP 服务器提供的 API 文档能力才会随之更新。
七、两种生成模式的取舍小结
| 模式 | 命令 | 数据源 | 前置依赖 | 适用场景 |
|---|---|---|---|---|
| 生产文档 | ./build https://doc.plugins.penpot.app | 线上发布版 API 文档 | pixi | 对齐线上公开 API、无需本地构建 |
| 本地文档 | pnpm run build:types | 仓库内当前源码经 TypeDoc 构建的 plugins/dist/doc | Caddy、pnpm | 修改 API 类型/注释后同步生成最新 YAML |
需要说明的边界:TypeDoc 本地构建依赖 plugins 子工作区中 plugins/package.json 声明的typedoc等开发依赖及其配置;而脚本抓取的是 TypeDoc 输出中的特定 CSS 类结构(tsd-*系列),因此若上游 TypeDoc 的页面结构发生大版本变化,可能需要同步修订prepare_api_docs.py中的选择器规则——这正是上文保留debug_type_conversion这类调试入口的原因。
【免费下载链接】penpotPenpot: The open-source design platform for Product teams that need scalable collaboration.项目地址: https://gitcode.com/GitHub_Trending/pe/penpot
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考