开源 CAD 通过 MCP 让 Agent 编写脚本
开源浏览器 CAD 查看器 mlightcad 新增 MCP 服务,让 Agent 编写 JavaScript 操作图纸。它以脚本 API 承载查询、分组和报告等组合任务,减少为每类需求不断增加独立工具的负担。
AI 深度解读
给 AI 接入 CAD,不一定要把画圆、查图层、量尺寸分别包装成工具。mlightcad 作者尝试了另一条路:只提供打开图纸和执行脚本两个 MCP 工具,让模型编写 JavaScript,在受控环境里组合 CAD API,完成查询、修改和导出。
作者用物料清单提取、标题栏识别、多图框拆分三个实验说明,这种方式能覆盖开发者没有预先实现的工作流。对正在做 AI 应用集成的程序员,更值得看的是:哪些逻辑应该交给模型,哪些计算应该留在应用内部,以及执行之后怎么验收。
两个工具为什么能覆盖复杂的 CAD 工作流
CAD 操作很难穷举。查找圆、按图层分组、提取区域文字都不复杂,但用户可能把它们组合成「找出半径小于 5 的圆,按图层分组,再输出 ID 和半径」。
如果每种组合都新增工具,最初的 20 个工具会不断膨胀。原文用 100 个 CAD 工具、500 道菜的菜单来形容这种维护负担,它们是比喻,并非性能测试结果。
mlightcad 的做法是提供一个统一的 cad 对象:cad.drawing、cad.entities、cad.tables、cad.blocks、cad.selection 用于读取数据,cad.measure 用于测量,cad.edit.transaction 用于修改,cad.file.saveDxf 用于导出。模型负责生成组合逻辑,CAD 环境负责执行。
工具负责连接应用,脚本负责组合能力。
这也改变了数据传递方式。假设图纸有 100,000 个实体,脚本可以在应用内部完成筛选,只把结果返回给模型。原文还用逐个检查 10,000 个对象举例,解释批处理的价值。这些数字都是说明性场景,作者没有提供耗时、token 或费用对照。
安全边界则由沙箱和校验器承担。按作者描述,脚本不能导入任意模块,也不能访问浏览器页面,只能使用受限 API。不过,原文没有展开超时、资源限制等机制,不能仅凭「有沙箱」就认定执行环境足够可靠。
共享核心让不同入口使用同一套能力
mlightcad 没有给内置聊天和外部 MCP 客户端分别实现一套 CAD 逻辑,而是拆成三个包。

@mlightcad/cad-agent-core 提供脚本环境和工具目录,核心目录目前只有一个执行脚本的工具。@mlightcad/cad-agent-plugin 把能力接入 cad-viewer 和 cad-simple-viewer 的聊天入口。@mlightcad/cad-mcp-server 再补上打开图纸的工具,供外部客户端使用。
MCP server 通过 stdio 通信,首次工具调用时用 Playwright 启动 Chromium,运行相同的 cad-simple-viewer 内核。浏览器会话跨调用保留;无头模式空闲超时后关闭,可见模式可以观察操作过程。
作者还计划支持 WebMCP。工具注册已抽成 registerCadMcpTools(),未来可以复用浏览器中已有的 CAD 文档管理器,省去另启 Chromium。这里属于架构计划,不能当作已交付功能。
原文使用预览包 @mlightcad/cad-mcp-server@1.7.8-preview.1。Claude Code / Claude Desktop 的 MCP 配置以 npx 为 command,args 依次为 -y、--package=@mlightcad/cad-mcp-server@1.7.8-preview.1、cad-mcp-server。Playwright Chromium 的安装命令是 pnpm --filter @mlightcad/cad-mcp-server install-browser;在 monorepo 内则应使用 scripts/run-published.mjs 启动器。
打开图纸可传入 {"path":"./plan.dxf","mode":"write"},也支持 HTTP、HTTPS URL;增加 "visible":true 就能观看执行过程。
下面的脚本展示了创建圆之后如何校验结果:
let id
cad.edit.transaction('circle', tx => {
id = tx.drawCircle({
center: { x: 0, y: 0 },
radius: 100
})
})
cad.view.zoomExtents()
return cad.measure.report([
cad.measure.check(
'radius',
100,
cad.measure.radius(id)
)
])
关键是最后的 cad.measure.radius(id):它重新读取创建结果,再与预期半径 100 比较。
随后调用 cad.file.saveDxf('./plan-out.dxf'),模型收到路径、大小等元数据,不必接收整份 DXF。需要视觉检查时,可以调用 cad.view.capturePreview(),把预览附到响应中。
三个实验真正验证了什么
第一个实验是在齿轮泵装配图中提取 BOM,也就是物料清单。难点在于,这张表由线条和文字拼成,并不是现成的表格对象。

Agent 先提取 Text、MText、Attribute 的文字与坐标,再用下面的代码把相近高度的文字归到一行:
const rows = new Map()
for (const t of texts) {
const key = Math.round(t.pos.y / 10)
if (!rows.has(key)) {
rows.set(key, [])
}
rows.get(key).push(t)
}
要看的是 Math.round(t.pos.y / 10)。这里按固定步长 10 分桶,依赖图纸单位、行距和文字位置;同一视觉行也可能跨过桶边界。它是这次实验使用的规则,不能直接当成通用表格解析算法。
之后,脚本按 Y 坐标从上到下排列各行,再按 X 坐标从左到右排列文字,返回结构化数据,由模型生成 Markdown 表格。
第二个实验是读取图框和标题栏。Agent 把包围盒面积最大的闭合 Polyline 当作图框,用 cad.entities.inBox() 收集内部文字,再整理为包含 project、title、drawnBy、checkedBy、scale、date、sheetNo、rev 的 JSON。示例值包括比例 1:50、日期 2026-10-12、图号 A-102 和修订号 B。
按原文描述,块属性会自动纳入查询,因此带属性的标题栏块无需单独处理。整个过程没有修改几何对象。
第三个实验是把包含三个图框的文件拆成独立 DXF。

Agent 收集闭合多段线,用 frames[1].extents 取得第二个候选框的范围,保留框内实体,通过 cad.edit.transaction 删除其余实体,再保存到 ./sheet-02.dxf,返回 kept、removed 数量。
每次导出后,它重新打开原始图纸,再处理下一张,避免在已经删减的文件上继续操作。作者报告三个文件均非空,但文件非空只能证明产生了输出,不能证明实体归属完全正确。
我的判断
我赞同把组合逻辑放进脚本。对于已有查询、编辑、导出 API 的复杂应用,这比持续新增业务专用工具更灵活。共享核心也能减少内置 Agent 与外部客户端之间的能力差异。
但这套方案的前提是,应用已经有一套足够清晰、稳定的脚本 API。接口文档、错误信息、事务和校验能力都要有人建设,维护成本只是换了位置。
三个实验验证的是可行性。原文没有给出样本数量、成功率,也没有与大量专用工具或固定解析程序做对照,所以「脚本更好」目前仍是作者的经验判断。
图框识别尤其需要谨慎:最大闭合多段线未必是图框,闭合多段线列表也未必按图纸顺序排列。跨框实体如何处理、坐标查询怎样处理边界,原文没有说清。
事务可撤销、文件非空,都不能代替业务正确性检查。
对于固定模板、重复运行的生产任务,确定性程序仍然便于验收。比较合适的路径是先让 Agent 探索组合方式,规则稳定后,再判断是否固化成普通代码。
能拿来做什么
- 如果你在给复杂前端应用接入 AI,可以先盘点现有领域 API,把打开文档等生命周期操作做成明确工具,再让受限脚本组合查询和批处理。
- 首批任务可以选择文字提取、实体统计等只读分析,让脚本返回 JSON 摘要,并保留源对象标识,方便人工抽查。
- 涉及批量删除或拆分导出时,应从原始文档分别生成结果,同时检查保留数量、关键内容和视觉预览,别把文件大小当作唯一验收条件。
- 对 BOM 行分组、图框定位这类临时规则,应先用不同布局检验适用范围;如果任务模板固定,再把经过验证的规则固化为可重复运行的程序。