Penpot 智能开发环境指南:.devenv 目录如何为多工作区 AI 编码客户端生成 MCP 配置
【免费下载链接】penpotPenpot: The open-source design platform for Product teams that need scalable collaboration.项目地址: https://gitcode.com/GitHub_Trending/pe/penpot
本文解析 Penpot 仓库中.devenv/目录的设计与实现:它如何通过「共享配置 + 端口占位符模板 + 合并生成脚本」三层结构,为 Claude Code、opencode、VS Code Copilot 与 OpenAI Codex CLI 四类 AI 编码客户端,按工作区(ws0/ws1/…)动态生成指向 Penpot MCP 与 Serena MCP 服务器的配置文件。读完后你将掌握该配置体系的完整目录布局、merge-mcp-config.py生成器的两种工作模式与manage.sh的调用链,并能独立完成手工启动、覆盖条目与端口排错。
背景:为什么需要按工作区生成 MCP 配置
Penpot 的开发环境(devenv)支持并行多实例:ws0绑定当前活跃仓库,ws1、ws2…… 则是在PENPOT_WORKSPACES_DIR下的独立克隆工作区。每个工作区跑着各自一套 Penpot 后端、MCP 服务与 Serena 代码索引服务,其宿主端口按工作区编号偏移(manage.sh中以端口基数PENPOT_PORT_BASE_MCP、PENPOT_PORT_BASE_SERENA加偏移计算,端口基数定义见 defaults.env 中的PENPOT_MCP_SERVER_PORT=4401与SERENA_EXTERNAL_PORT=14181)。
由于 Penpot MCP 与 Serena MCP 的端口是工作区专属的,让 AI 编码客户端直连正确的服务,就必须为每个工作区生成一份带正确端口的 MCP 配置——这正是 .devenv/README.md 所描述的机制的核心目标。
目录布局与各文件职责
.devenv/的实际布局(与 README 一致):
.devenv/ README.md scripts/ merge-mcp-config.py # manage.sh 调用的生成器 shared/ # 已提交;与工作区无关的条目 claude-code.json # Playwright,所有工作区内容相同 opencode.json vscode.json codex.toml templates/ # 已提交;含 ${...} 端口占位符的条目 claude-code.json # Penpot MCP、Serena MCP,端口是唯一差异 opencode.json vscode.json codex.toml mcp/ # gitignored;manage.sh 按工作区写入 claude-code.json # 通过 Claude Code 的 --mcp-config 加载 opencode.json # 通过 OPENCODE_CONFIG 环境变量加载除此之外还有一个生成在.devenv/之外、位于 VS Code 自动发现路径的文件(gitignored):
.vscode/mcp.json # 由 VS Code 中的 GitHub Copilot 自动加载各部分的职责边界非常清晰:
| 目录/文件 | 是否入库 | 内容 | 特点 |
|---|---|---|---|
shared/ | 是 | 不依赖工作区的 MCP 条目(目前是 Playwright 浏览器驱动服务器) | 静态文件,所有工作区内容相同 |
templates/ | 是 | 依赖工作区的条目(Penpot MCP、Serena MCP),含${PENPOT_MCP_PORT}、${SERENA_MCP_PORT}占位符 | 占位符按工作区由manage.sh中的端口基数常量解析 |
mcp/ | 否(gitignored) | shared/与端口替换后templates/的合并结果 | 每次run-devenv --agentic时由manage.sh重写,禁止手改 |
.vscode/mcp.json | 否(gitignored) | 同样的合并结果,但写在 VS Code 自动发现路径 | 因ws0上该文件就是活跃仓库自身文件,reconcile 采用深合并保留开发者自有条目 |
一个关键例外是Codex CLI:它无法从任意路径加载 MCP 配置,而唯一的项目级配置文件.codex/config.toml可能已被开发者本人占用。因此 Penpot不为 Codex 写任何文件——start-coding-agent codex在启动时由shared/codex.toml+templates/codex.toml现场构建-c命令行覆盖注入(源码见 manage.sh 的 start-coding-agent)。
四个客户端的配置 schema 差异
不同 AI 客户端对 MCP 配置文件的顶层键与条目结构要求不同,Penpot 为每个客户端维护一套shared/与templates/文件。以 Claude Code 为例:
.devenv/shared/claude-code.json(工作区无关条目,顶层键mcpServers):
{ "mcpServers": { "playwright": { "command": "npx", "args": ["@playwright/mcp@latest", "--cdp-endpoint=http://127.0.0.1:9222"] } } }.devenv/templates/claude-code.json(工作区相关条目,端口走占位符,两个 HTTP 型 MCP 服务器均通过mcp-remote桥接为 stdio 进程):
{ "mcpServers": { "penpot": { "command": "npx", "args": ["-y", "mcp-remote", "http://localhost:${PENPOT_MCP_PORT}/mcp", "--allow-http"] }, "serena-devenv": { "command": "npx", "args": ["-y", "mcp-remote", "http://localhost:${SERENA_MCP_PORT}/mcp", "--allow-http"] } } }其余三个客户端的 schema 差异(见 shared/opencode.json、shared/vscode.json、shared/codex.toml 及对应 templates):
- opencode:顶层键为
mcp;本地条目用type: "local"+command数组(如["npx", "@playwright/mcp@latest", "--cdp-endpoint=http://127.0.0.1:9222"]),远程条目直接type: "remote"+url+enabled,无需mcp-remote桥接; - VS Code Copilot:顶层键为
servers;本地条目type: "stdio"+ 字符串command+args数组,远程条目type: "http"+url; - Codex CLI:TOML 格式,条目放在
[mcp_servers.<name>]表下;本地服务器写command/args,远程服务器只写url(见 templates/codex.toml)。
其中 Playwright 条目的--cdp-endpoint=http://127.0.0.1:9222指向 devenv 中浏览器实例的远程调试端口,因此它在shared/而非templates/中——对全部工作区一致。
生成器:merge-mcp-config.py 的两种模式
.devenv/scripts/merge-mcp-config.py 是整个体系的生成器,由manage.sh的write-instance-mcp-configs(JSON 客户端)与start-coding-agent(Codex)分别调用。其 CLI 契约(源自文件头部 docstring):
merge-mcp-config.py --format json --key <key> [--merge-into-existing] \ <shared> <template> <out> merge-mcp-config.py --format codex-args <shared> <template>退出码:0成功,2参数错误。两个模式的行为:
1.json模式 —— 深度合并并写出文件。两个 JSON 文档在可配置的顶层键(Claude Code 用mcpServers、opencode 用mcp、VS Code 用servers)之下合并。合并优先级从低到高为:
- 已存在的
<out>文件(仅当指定--merge-into-existing); shared块;template块(同名条目覆盖 shared)。
实现上见 merge_json 函数:顶层做{**base, **shared}的浅合并,而key之下的条目按名称三级合并{**base.get(key, {}), **shared.get(key, {}), **tpl.get(key, {})}——模板条目在名称冲突时胜出,底层各层贡献的其余条目全部保留。--merge-into-existing专门用于 VS Code 的.vscode/mcp.json:在ws0上该文件就是开发者活跃仓库自己的文件,可能含开发者自己的服务器条目,所以必须作为最低优先级层先加载;而 Claude/opencode 的输出位于专用、gitignored 的.devenv/mcp/路径,无开发者内容,直接干净覆盖。
2.codex-args模式 —— 打印-c赋值供命令行注入。两个 TOML 块经_deep_merge深度合并(实现:递归合并,标量/列表键以覆盖层胜出),再由_flatten将嵌套表展开为点号键的叶子序列(列表作为 TOML 数组是叶子,不递归),最终每行输出一条dotted.key=<toml-value>赋值,调用方为每行套上codex -c。值序列化见 _toml_value:布尔先于整数判断(因isinstance(True, int)为真),字符串以 JSON 字符串形式输出(对 ASCII 值即是合法 TOML basic string)。之所以选择临时覆盖而非写.codex/config.toml,docstring 中给出明确理由:Codex 无法从任意文件路径加载 MCP 配置(CODEX_HOME会同时挪走认证与历史),写自动发现的.codex/config.toml则会覆盖开发者的项目级配置。
占位符解析:两种模式下${VAR}占位符统一用 Python 的os.path.expandvars从当前环境变量解析(实践中只有 template 块带占位符)。未定义的占位符保留${VAR}字面文本——调用方(manage.sh)负责在调用前导出变量。
manage.sh 中的调用链与端口解析
manage.sh中与该体系相关的实现分三处:
_merge-mcp-config-json助手(manage.sh#L382-L387):对 JSON 客户端的薄封装,转发参数调用python3 .devenv/scripts/merge-mcp-config.py --format json --key <key> <shared> <template> <out>,额外标志(如 VS Code 输出所需的--merge-into-existing)原样透传。Codex 有意不经过这个助手。
write-instance-mcp-configs(manage.sh#L412-L453):run-devenv --agentic每次通过时为工作区生成 MCP 配置。流程:
- 解析工作区目录:
ws0即$PWD,ws1+取workspace-path克隆路径; - 校验
.devenv/shared与.devenv/templates存在,否则跳过(避免在无该目录的旧克隆上崩溃); - 创建
.devenv/mcp/与.vscode/目录; - 从端口基数常量计算端口并导出:
PENPOT_MCP_PORT=$(instance-port "$instance" "$PENPOT_PORT_BASE_MCP")、SERENA_MCP_PORT=$(instance-port "$instance" "$PENPOT_PORT_BASE_SERENA")。端口基数在 manage.sh#L56-L58 取自defaults.env(PENPOT_MCP_SERVER_PORT、SERENA_EXTERNAL_PORT),defaults.env 中注释说明并行工作区按10000*N偏移端口,因此ws0的 Penpot MCP 在4401、Serena 在14181,ws1则偏移一万; - 依次生成三个输出:
.devenv/mcp/claude-code.json(key=mcpServers)、.devenv/mcp/opencode.json(key=mcp)、.vscode/mcp.json(key=servers,带--merge-into-existing)。
start-coding-agent(manage.sh#L945-L1055):统一的启动包装器,下一节展开。
启动 AI 编码客户端
最简单的路径是使用包装命令——它知道每个客户端的启动标志、cd到目标工作区、并拒绝在目标实例未运行或 MCP 配置未生成时启动(避免发出每次工具调用都报错的会话):
# 默认目标是 ws0(活跃仓库)。 ./manage.sh start-coding-agent claude [...转发参数] ./manage.sh start-coding-agent opencode [...转发参数] ./manage.sh start-coding-agent vscode [...转发给 'code' 的参数] ./manage.sh start-coding-agent codex [...转发参数] # 用 --ws N 指定并行工作区。N 必须是非负整数; # 'main'、'ws1' 这类拼写会被拒绝(parse-ws-integer 校验)。 ./manage.sh start-coding-agent claude --ws 1 ./manage.sh start-coding-agent opencode --ws 2包装器的守卫逻辑(见源码):
- 客户端名必须是
claude|opencode|vscode|codex之一,否则报错; - 目标实例未运行(
devenv-main-running检查)时提示先执行./manage.sh run-devenv --agentic(ws1+追加--ws N); - 客户端二进制不在
PATH上时提示安装; - 对应配置文件缺失时提示先跑
run-devenv --agentic(Codex 例外:cfg_rel指向的是已提交的模板.devenv/templates/codex.toml,因为 Codex 没有生成文件)。
等价的纯手工启动方式(在工作区目录内执行):
claude --mcp-config .devenv/mcp/claude-code.json OPENCODE_CONFIG=.devenv/mcp/opencode.json opencode code "$PWD" # VS Code 自动发现 .vscode/mcp.json # Codex:把我们的服务器作为 -c 覆盖传入(不写配置文件)。 codex $(python3 .devenv/scripts/merge-mcp-config.py --format codex-args \ .devenv/shared/codex.toml .devenv/templates/codex.toml \ | sed 's/^/-c /')start-coding-agent codex替你完成-c装配(且先解析工作区端口):它把PENPOT_MCP_PORT/SERENA_MCP_PORT导出到环境,调用--format codex-args逐行读取输出并组装-c参数数组,最后exec codex "${codex_args[@]}" "$@"。由于我们的服务器以命令行覆盖形式进入,Codex 的 "trusted project" 提示不涉及它们——该提示只拦截 Codex 自己的.codex/config.toml,而 Penpot 从不写它。
覆盖 Penpot 管理的条目
自动发现的配置与启动器加载的配置都位于开发者全局配置之上(优先级规则各有不同)。四个客户端均提供遮蔽(shadow)官方条目的逃生通道:
- Claude Code——
claude mcp add --scope local …安装私有条目,覆盖mcp/claude-code.json中的同名条目,本地作用域胜出; - opencode—— 在仓库根目录放一个
opencode.json写入覆盖条目。opencode 的优先级链是global →OPENCODE_CONFIG→ project,项目文件始终胜出。根目录的opencode.json被有意 gitignore,因为这类覆盖是个人性的; - VS Code Copilot—— reconcile 深合并进
.vscode/mcp.json,你自己添加的服务器会被保留(只有penpot、serena-devenv、playwright三个条目被重写)。要遮蔽官方其中之一,在你 VS Code 用户资料的 MCP 配置中放同名单条——它随工作区文件一起加载且胜出; - Codex CLI—— 我们的服务器以
-c覆盖进入,这是 Codex 的最高优先级层,胜过~/.codex/config.toml或项目.codex/config.toml中的同名[mcp_servers.<name>]。要覆盖官方其中之一,在客户端名后追加自己的-c——额外参数转发在官方参数之后,后出现的-c胜出,例如./manage.sh start-coding-agent codex -- -c 'mcp_servers.penpot.url="…"'。
适用前提与限制
- 端口解析依赖
manage.sh的instance-port(端口基数 + 工作区偏移),manage.sh在 defaults.env 提供的环境上下文中运行——脱离manage.sh手工调用生成器时,必须自行导出PENPOT_MCP_PORT与SERENA_MCP_PORT,否则输出中会残留${...}字面占位符; .devenv/mcp/下的文件每次run-devenv --agentic会被干净重写,不要手工编辑;- 仅当目标 devenv 实例处于运行状态时客户端才有可连的 MCP 服务,
start-coding-agent会显式检查并拒绝启动; - 客户端级配置 schema(浏览器远程调试、不支持客户端的手工搭建等)的更完整说明见 agentic-devenv 文档。
小结
.devenv/用一个「静态共享块 + 端口模板块 + 按工作区合并」的三件套,优雅解决了 Penpot 并行开发环境下四类 AI 客户端 MCP 配置的端口漂移问题:生成器merge-mcp-config.py负责深合并与占位符替换(json模式写出文件、codex-args模式打印命令行覆盖),manage.sh负责端口计算与守卫式启动,而每个客户端的覆盖逃生通道(local scope、项目级opencode.json、用户资料条目、追加-c)保证了开发者个人配置不会被工具链吞没。
【免费下载链接】penpotPenpot: The open-source design platform for Product teams that need scalable collaboration.项目地址: https://gitcode.com/GitHub_Trending/pe/penpot
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考