开源 3D 建筑编辑器 Pascal Editor:为什么要给 Agent 单独写一份使用指南
本文基于 Pascal Editor 仓库 commit 64dca3d(2026-08-04)梳理,该项目仍在高频迭代,具体行为以仓库 https://github.com/pascalorg/editor 最新代码与文档为准。
让 Agent 操作专业软件,卡住的地方几乎从来不是”功能没包成工具”,而是这个领域里那些”不用说也知道”的常识没人写下来。 Pascal Editor 这个开源的浏览器端 3D 建筑编辑器(跟 Pascal 编程语言、跟压强单位帕斯卡都没有关系,是 Pascal Group Inc. 的建筑建模项目,MIT 许可证)在它的 MCP 服务器里给出了一个很直白的答案:除了工具本身,再单独放一份写给 Agent 的使用指南,和一组把领域默认值钉死的场景引导提示词。
这两份文件加起来不到两百行纯文本,但它们拦下来的每一条,基本都是通用 Agent 面对专业工具时会摔的一跤。本文只看设计取向,不排座次。
站内已经有几篇相邻的文章:MCP 常见误解讲协议层面容易被理解错的地方,工具描述怎么写讲单个工具的 description 字段该承载什么,开源 Agent 项目三体对比横向比不同项目的架构选择;这一篇的分工是往下一层走——当工具本身已经写完了,剩下那些塞不进任何单个工具描述的领域约束,该放在哪、长什么样。
一、工具齐全不等于 Agent 会用
先给个体量感。在 packages/mcp/src/tools/ 目录下用 grep -rn "server.registerTool" -A1 数,注册的工具名有 46 个,从 create_wall、cut_opening 这种贴着几何走的,到 create_house_from_brief、furnish_room 这种一步到位的语义工具都有。工具够多了吧。
但工具多本身就是新问题。建筑建模的操作是有偏序的:你不可能先摆家具再砌墙,也不可能在还没有楼层的地方开门。packages/mcp/src/resources/agent-guide.ts 里的这条规则就是在说这件事:
- For rooms, use `create_room` -> `add_door` -> `add_window` -> `furnish_room`.
四步顺序,一个通用 Agent 从工具名上是看不出来的。它只会看到四个可用工具,然后按自己认为合理的顺序调——而”合理”在没有领域知识时通常等于”按用户提到的顺序”。
再看另一层:packages/nodes/src/ 下有 46 个子目录,其中 shared/ 不是节点类型,也就是 45 种节点类型;整个 packages/nodes 包 733 个受版本控制的文件,全仓 2508 个。wiki/architecture/ 下有 20 份 md(1 份 README 索引加 19 篇分主题)。这就是 Agent 面对的领域模型的真实规模。指望它现场读懂再动手,是不现实的。
所以 agent-guide.ts 的第一句话直接堵死了这条路:
You are editing Pascal architectural projects. Use MCP tools only; do not
inspect the Pascal repository unless the user explicitly asks.
只用 MCP 工具,除非用户明确要求,否则不要去翻仓库。这在编码 Agent 的世界里是反直觉的——大家平时调教 Agent 的习惯恰恰是”不懂就去读源码”。但在这里,读源码是纯粹的成本:几千个文件读进上下文,换来的领域理解还不如这份两百行的指南密。
顺带一个对照:这个仓库根目录同时存在 AGENTS.md、CLAUDE.md、GEMINI.md 三份 Agent 约定文件,那是给”来改这个仓库代码”的 Agent 看的;而 packages/mcp/src/resources/agent-guide.ts 是给”来用这个编辑器建模”的 Agent 看的。同一个项目,对两类 Agent 给了两套完全不同的说明书,边界划得很清楚。
二、这份指南在拦的六类错
把 agent-guide.ts 逐节读下来,能看出它拦的不是随机的错,而是六类结构性的错。
第一类是概念对齐。 指南单开了一节 Important Concepts,解释 project 是浏览器可见的容器,scene graph(场景图,就是把墙、楼层、家具这些对象按父子关系挂成一棵树的数据结构)是建筑模型本身,draft(草稿)是浏览器当前显示的工作模型、可以被反复覆盖,version/checkpoint 才是一次有意义的存档。这四个词在通用 Agent 的词表里是模糊的,不写清楚,它就会把”保存”理解成一个动作而不是两种模式。
第二类是禁止推断 URL。 指南写得很硬:Return the final editorUrl from tool output. Do not infer routes.——用工具返回的 editorUrl,不要自己推路由。它甚至补了一句”托管版编辑器 URL 形如 /editor/<projectId>”,然后紧接着说如果工具只返回了 id,就去调 get_project_status 拿浏览器 URL。这是典型的”我知道你会猜,所以我告诉你正确答案的同时也告诉你不许猜”。Agent 编 URL 是个很常见的失败模式,因为路由格式看起来太好推了。
第三类是层级不变式。 这条在 packages/mcp/src/prompts/from-brief.ts 里写得更细:楼层挂在建筑下,墙、栅栏、分区(zone,平面上圈出来的一块功能区域,比如”这一圈是卧室”)、楼板(slab,一层的水平承重面,也就是你脚下踩的地面兼下层的顶)、天花、屋顶、楼梯挂在楼层下,门窗挂在墙下(parentId = wallId),地面家具挂在楼层下、挂墙和吸顶的物件挂在对应的墙或天花下,不要把物件直接挂到 site 节点。这些是数据模型的硬约束,违反了不一定立刻报错,但渲染出来就是错的。
第四类是不要绕过语义层。 指南说优先用语义工具,别手写节点图,除非确实没有对应的语义工具。这条有意思,因为它跟仓库里另一份文档存在张力——packages/mcp/examples/generate-apartment.md 这个端到端示例,从头到尾演示的是手写 apply_patch 批量创建墙体:
// tool: apply_patch
{
"name": "apply_patch",
"arguments": {
"patches": [
{ "op": "create", "parentId": "level-1",
"node": { "type": "wall", "start": [0, 0], "end": [10, 0],
"thickness": 0.2, "height": 2.5 } }
]
}
}
示例文档和运行时指南给的示范路径不一样。真正会被注入到 Agent 上下文里的是指南和提示词,示例文档只有人会读。这本身也是一条经验:当你给 Agent 准备的说明和给人准备的文档分家之后,它们会以不同的速度过时,而 Agent 那份的过时是无声的。
第五类是收尾自检。 指南在 Required Final Checks 一节列了几条硬要求,其中三条最实在:save_scene 必须成功;verify_scene.hasIssues 应该是 false,否则要解释剩下的问题;get_project_status.nodeCount 对一个非空设计必须大于 0。最后一条尤其实在——它防的是 Agent 兴高采烈地报告”公寓已建好”,而场景里一个节点都没有。
第六类是排障脚本。 指南专门写了一节叫 If Something Looks Empty,给了四步:调 get_project_status,比对 publishedVersion、latestVersion、browserVisibleVersion、nodeCount、graphHash 这五个字段;如果图非空但浏览器看着是空的,再调一次 get_project_status 重新绑定会话,然后用 saveMode: "draft" 调 save_scene;最后重跑 verify_scene。把一个已知的故障模式连同排查顺序一起写进指南,比让 Agent 现场瞎试划算得多。
三、场景引导提示词:把领域默认值钉死
packages/mcp/src/prompts/scene-guidance.ts 导出一个叫 SCENE_DESIGN_GUIDANCE 的字符串常量,from-brief.ts 和 iterate-on-feedback.ts 两个提示词都把它拼进自己的 preamble。它干的事跟指南不同:指南管流程,这份管数值。
最直白的几条:
- Door default: 0.9m wide by 2.1m high, floor-mounted.
- Window default: 1.5m wide by 1.5m high with a 0.9m sill height.
- For clear concrete requests, act with reasonable defaults instead of asking
for clarification.
门默认 0.9 米宽、2.1 米高、落地;窗默认 1.5 米见方,窗台高 0.9 米(窗台高指窗户下沿离地面的高度,这个数字决定了人站着能不能舒服地看出去)。然后是那句关键的:请求足够清楚具体时,直接用合理默认值动手,别反问。
这条值得单独拎出来说。Agent 反问是安全行为,但在建模场景里,一个门有多宽这种问题反问回去,用户既答不上来也不想答。把行业默认值写进提示词,等于替用户把这些问题提前答了。代价是这些默认值锁死了尺度——0.9×2.1 的门是住宅门,不是厂房卷帘门。
还有几条是把行业惯例翻译成 Agent 能执行的检查:
- 完整住宅要包含现实的辅助空间:厨房、起居/餐厅、卫生间、门厅走廊,必要时还有储藏和洗衣。通用 Agent 接到”三室一厅”的需求,很可能就真的只造三个卧室加一个客厅。
- 多层建筑要每层各建自己的 story shell(本层的外壳,也就是围一圈的外墙加相应楼板),明确禁止把一层的外墙拉高去顶替上层的墙。这是个纯粹的偷懒捷径,几何上看着能糊弄过去,但楼层分离的一切功能都会跟着坏掉。
- 用户说的”几层”是占用层数,不是原始层级数。
verify_scene同时返回levelCount和occupiedStoryCount,提示词明确要求校验层数时看后者。因为create_roof会在最顶层之上另建一个专用的屋顶层——这个层不住人,但在数据里它就是一层。提示词还补了一句:这种专用屋顶/支撑层是允许存在的,不许为了凑层数把它删掉。
工具流那一段则是把”先看后动”变成硬要求:编辑已有场景先用 list_levels、get_level_summary、get_walls、get_zones 查;先 create_level 再每层一次 create_story_shell 把体量立起来(体量指建筑的外形轮廓和楼层堆叠关系,先把这个立住、内部细节后补);跨层楼梯用 create_stair_between_levels,理由写在原文里——这样楼板和天花的开洞能保持矩形,也不会跟自动生成的洞重复(楼梯要穿楼板,就得在楼板上挖一个洞,手动挖和自动挖撞在一起是个真实的坑);放具体家具前先 search_assets 再 place_item;门窗位置用 t = 0..1 表示沿墙的相对位置,0 是起点、0.5 是中点、1 是终点;语义工具表达不了的批量精确编辑才用 apply_patch。每个大阶段结束调一次 get_level_summary 或读 pascal://scene/current/summary,理由也写明了:让进度可见,错误好定位。
还有一条散在 agent-guide.ts 里的空间常识:相邻房间之间尽量共用一面墙,每扇门两侧各留约 0.65 米净空(净空就是门前后不许被家具占住的活动空间,不然门开不开、人过不去),家具的占地轮廓不要互相叠压。这个 0.65 不是随口写的,packages/mcp/src/tools/door-clearance.ts 里有个常量 DEFAULT_DOOR_CLEAR_DEPTH = 0.65,文本约束和代码校验对得上。
四、这套东西由哪几块组成
| 组成部分 | 它负责什么 | 仓库位置 | 你什么时候会碰到它 |
|---|---|---|---|
| Agent 使用指南 | 流程、概念、URL 纪律、收尾自检、排障脚本 | packages/mcp/src/resources/agent-guide.ts | Agent 主动读 pascal://agent-guide 资源时 |
| 场景设计守则 | 单位、门窗默认尺寸、层级规则、分阶段工具流 | packages/mcp/src/prompts/scene-guidance.ts | 被 from_brief / iterate_on_feedback 拼进提示词 |
| 提示词 | 从简报建场景、按反馈最小改动、照片改造 | packages/mcp/src/prompts/ | 宿主 UI 里选中某个 prompt 时 |
| 工具层 | 46 个注册工具,从建墙到整屋生成 | packages/mcp/src/tools/ | Agent 每次实际动手 |
| 场景资源 | 当前场景快照、人读摘要、物料目录、逐层约束 | packages/mcp/src/resources/ | Agent 查进度、对照约束时 |
| 存储层 | SQLite 场景库,唯一的生产存储后端 | packages/mcp/src/storage/sqlite-scene-store.ts | 场景落盘、跨会话找回时 |
| HTTP 传输 | 把 MCP 服务挂到本地端口,带鉴权和来源白名单 | packages/mcp/src/transports/http.ts | 不走 stdio 而走 HTTP 接入时 |
| 跨包改动记录 | 记录为了让 MCP 跑起来改了哪些包、代价是什么 | packages/mcp/CROSS_CUTTING.md | 想搞清楚它对主仓库的侵入面时 |
| 端到端示例 | 一次完整会话的工具调用流水 | packages/mcp/examples/generate-apartment.md | 人上手前通读一遍 |
五、边界与代价:它明确不管什么
这套设计不是免费的,CROSS_CUTTING.md 这份文件本身就是维护者把代价摊开写的诚实做法。
指南是纯文本,没有强制力。 它作为 MCP 资源和提示词文本存在,模型读不读、遵不遵守,服务端管不了。verify_scene 和 validate_scene 能兜住一部分——从 scene-query.ts 里罗列的 issue 文本看,它报的是空楼层、门窗没挂在墙上、开洞超出所在墙体、有墙没门、有分区没楼板或没天花、以及 schema 校验错误这类;但”该用语义工具却手写了图”这类违规,工具层是不拦的。
默认值锁死了尺度。 门 0.9×2.1、窗 1.5×1.5 台高 0.9、墙厚 0.1–0.3 米、层高 2.4–3.0 米,加上”完整住宅要有厨房卫生间门厅”这一整套辅助空间清单,全都是住宅假设。做厂房、展馆、地下车库,这些默认值不但帮不上忙,还会主动把 Agent 往错的方向推。提示词里那句”别反问,直接用默认值”在这种场景下会放大错误。
它明确不管建筑规范。 示例里用户在 constraints 参数里写了”Spanish building regulations; ceiling height 2.5 m”,那是当作自由文本原样拼进提示词的。仓库里没有任何规范库或合规校验,validate_scene 校的是数据有效性,verify_scene 校的是布局层面的实用问题,都不是合规检查。
数据落在你的机器上。 存储后端是 SqliteSceneStore,用内置的 SQLite 驱动(MCP CLI 用 bun:sqlite,Next.js 编辑器服务端用 node:sqlite),默认写到 ~/.pascal/data/pascal.db。路径可以用 PASCAL_DB_PATH 指定具体文件、用 PASCAL_DATA_DIR 指定目录,另有 PASCAL_MAX_SCENE_BYTES 限制单个场景的字节数。Windows 下默认走 %APPDATA%/Pascal/data/pascal.db,也支持 $XDG_DATA_HOME。你需要知道这个文件在哪,因为 Agent 建的所有东西都在里面,而且 delete_scene 是注册工具之一——Agent 有权删。
暴露端口意味着什么。 HTTP 传输默认绑 127.0.0.1,代码里有一道硬闸:主机不是回环地址时,必须提供 PASCAL_MCP_HTTP_TOKEN 或显式传入 authToken,否则直接抛错拒绝启动。CORS 来源白名单走 PASCAL_MCP_HTTP_ORIGINS(逗号分隔),另有按分钟计的限流。这些默认值是对的,但含义要说清楚:一旦你把它挂到非回环地址上,任何能访问这个端口并持有 token 的进程,都能读写删你的全部场景。
它按配置可能发起出网请求。 CROSS_CUTTING.md 第 5 节记了一次安全评审的结果:场景里的 URL 字段以前是裸 z.string(),攻击者构造的场景把贴图 URL 写成 javascript:alert(1) 或者 http://169.254.169.254/latest/meta-data/,编辑器渲染时就会外联或外泄。现在加了 AssetUrl 校验器,白名单是 asset://、blob:、data:image/、应用内相对路径、https://,以及指向 localhost / 127.0.0.1 的 http://;可以用 PASCAL_ALLOWED_ASSET_ORIGINS 进一步收窄来源。同一节里维护者自己列了还没堵上的口子:item.asset.thumbnail 仍是裸字符串没上校验,data:image/svg+xml 因为前缀匹配能过关但 SVG 可以带内联脚本。这些是仓库里白纸黑字写着的已知缺口,不是我的推测。
还有些不一致是记录了但没修的。 SiteNode.children 存的是完整节点对象,而其它容器节点(building、level、wall、ceiling、roof、stair)存的都是 id 字符串数组。这导致同一个 building 在扁平字典和 site 的 children 里各存一份、更新不同步。维护者的说明是改 schema 会破坏已序列化的场景数据,超出这次改动的范围,MCP 侧的绕法是全部通过扫 parentId 来解析父子关系,不管 schema 用的哪种表示。
最后一条代价挺典型: 为了让 MCP 服务能在 Node 里跑,@pascal-app/core 加了几个 subpath 导出。原因是主入口会 re-export 全部 System*,而这些系统会副作用地引入 three、three-mesh-bvh、three-bvh-csg;在没有浏览器的 Node 环境里,three-mesh-bvh 的 CJS UMD 构建在模块加载阶段就解析不到 three 的全局对象,于是仅仅写一行 import { WallNode } from '@pascal-app/core' 就会在你任何代码跑起来之前崩掉。新增的 ./schema、./store 等 subpath 指向不会传递拉进图形代码的模块,绕开了这个问题。想让 Agent 操作一个原本只为浏览器设计的工具,这类”把渲染和数据切开”的活是躲不掉的。
六、上手与避坑清单
别让 Agent 自己拼编辑器 URL。 会踩是因为 /editor/<projectId> 这种路由太好猜了,模型看一眼就觉得自己会了,但项目 id、托管域名、草稿与已发布版本的对应关系它都不掌握。避法就是指南里那句:只用工具返回的 editorUrl;只拿到 id 就去调 get_project_status。
别把 checkpoint 当自动保存用。 会踩是因为 save_scene 的两种 saveMode 名字都像”保存”,模型倾向于选看起来更稳妥的那个,结果每改一面墙就存一个版本。指南的划法很清楚:日常进度用 saveMode: "draft",只有到了真正的里程碑才用 "checkpoint"。
层数对不上先看是不是屋顶层。 会踩是因为 create_roof 会在顶层之上另建一个专用屋顶层,Agent 数 levelCount 发现比用户要的多一层,就动手删。避法是校验层数只看 verify_scene 返回的 occupiedStoryCount,并且明确告诉 Agent 屋顶层合法、不许为凑数删。
浏览器里空白别急着重建。 会踩是因为 Agent 看到空视口的第一反应是”没建成功,重来一遍”,于是在已有数据上再叠一套。指南给的顺序是先诊断:调 get_project_status 比对 nodeCount 和三个版本号,图非空就重新绑定会话再存一次草稿,最后重跑 verify_scene。
别一上来就 apply_patch 手写节点。 会踩有个具体原因:仓库的示例文档演的就是手写 patch,人照着示例配好环境,Agent 又从别处学到 patch 更”底层可控”,两边一拍即合。但语义工具里封着的东西是 patch 没有的——furnish_room 会跳过或挪开挡住门前净空、跟其它家具重叠的摆位。避法是把语义工具当默认路径,apply_patch 只留给语义工具表达不了的批量精确编辑。
放具体家具前先搜。 会踩是因为 place_item 需要目录里的物料,Agent 不搜就直接填一个它认为存在的名字。提示词的要求是先 search_assets 再 place_item。
HTTP 传输别裸奔。 会踩是因为为了让某个远程宿主连上,随手把绑定地址从 127.0.0.1 改掉。代码会强制你给 token,但 token 之外的东西得你自己想:谁能访问这个端口、CORS 白名单 PASCAL_MCP_HTTP_ORIGINS 有没有配、这台机器上的 pascal.db 里有没有不该被改的场景。
门口那 0.65 米别省。 会踩是因为在平面图上看,门前那块地是空的,摆个柜子刚好。但那是通行净空,占了门就不能正常开合。furnish_room 会主动躲,verify_scene 和 check_collisions 会报剩余问题——别忽略这些报告。
收尾
把这两份文件读完,最值得抄走的其实是它的分层方式:单个工具的描述只解释这个工具本身,那些跨工具的顺序、领域默认值、层级不变式、以及”看起来失败了先查什么”,全部集中放在一份 Agent 能主动读的资源和一段被复用的提示词常量里。工具描述里塞不下的东西,需要一个专门的地方安放,而不是指望模型自己补齐。
如果你要照着做,可以拿这三个问题自查:你的领域里有没有”操作顺序错了不会报错但结果是错的”的情况?有没有一组用户答不上来、必须由你替他答的默认值?有没有一个已知的失败现象,值得把排查步骤直接写成给 Agent 的脚本?
想继续往下读仓库,建议的顺序是:packages/mcp/src/prompts/from-brief.ts(看那段层级不变式怎么和场景守则拼在一起)、packages/mcp/src/tools/scene-query.ts(看 verify_scene 到底检查了什么)、packages/mcp/CROSS_CUTTING.md(看为了把 Agent 接进来,主仓库付出了什么)。相关的工程话题可以接着看MCP 安全边界和Agent 改动边界约定。
本篇属于一个把开源3D 建筑编辑器 Pascal Editor逐层拆开讲的系列,整体地图见 Pascal Editor 是什么:浏览器里的开源 3D 建筑编辑器,与它自带的建模 MCP 服务器;沿着这条线往下,还可以看 3D 建筑编辑器 Pascal Editor:IFC 模型转换必然有损 和 Pascal Editor 仓库导读:3D 建筑编辑器怎样切开渲染层与编辑层。