
前阵子有个朋友问我“你说现在 AI Agent 能写代码、能订机票能不能让它自己剪一条视频出来”我说可以前提是你得给它一个能操控画布和剪辑的服务。于是我把这个本地画布 剪辑的工具做成开源项目并给它接上了 MCPModel Context Protocol让任何支持 MCP 的 Agent 都能全程操控它当成 libTV 的开源平替方案。项目跑通之后Claude Desktop、Cursor、Trae 这些主流 Agent 客户端都能对着本地画布指哪打哪从导入素材、左右移动画布、裁片段、加转场到导出成片全部由 Agent 端到端完成。这篇文章我会把整个项目的设计逻辑、架构拆解、部署接入方式、真实调用过程以及我踩过的坑都写出来。如果你正在研究 Agent 与视频创作工具的联动或者想找一个能跑在本地的、可被 AI 操控的剪辑工具这篇应该能帮你省下不少时间。1. 为什么放着现成的 libTV 不用我偏要自己搓一个1.1 libTV 很好但我需要的是“能被任意 Agent 调用”的能力libTV 本身的思路很吸引人通过提示词驱动画布上的视频创作让 AI 理解时间轴和片段。问题在于这类工具的提示词体系和交互方式往往和自家产品深度绑定你用它的提示词格式就得在它的环境里玩。一旦想把同样的操作交给不同的 Agent 客户端或者想在自己本地的素材库、自有渲染管线上跑自动化就发现根本接不进去。我实际遇到的情况更直接。团队里有人用 libTV 做过几条测试视频效果确实可以但当我们想把它并进现有的内容生产流程时卡住了第一素材都在内网服务器上我不想为了一次剪辑把几十 GB 的原始视频传到某个云端画布第二我需要让 Agent 根据我们自己的排期表自动决定剪哪段、用哪个模版这个逻辑在 libTV 的封闭环境里写不进去。MCP 协议出现后这个问题的解法突然清晰了。MCP 本质上是一个标准化的工具调用协议Agent 可以通过它发现工具、读取工具定义、调用工具并拿到结构化结果。只要我把“画布操作”和“剪辑操作”封装成一组 MCP 工具那么任何支持 MCP 的 Agent 都能直接操控不用关心我的底层是 Electron 还是 FFmpeg也不用为不同 Agent 适配接口。1.2 这个项目到底是什么本地画布 剪辑 MCP Server 三件套我开源的这个项目核心是三个部分组合在一起。第一部分是本地画布。它是一个运行在你电脑上的可视化工区能够展示视频片段在时间轴上的排列方式支持素材的拖入、片段的左右移动、预览帧的刷新。这里的“画布”不是简单的一块绘图板而是一个能看到当前剪辑进度的界面。第二部分是剪辑内核。它负责真正的视频处理读取媒体文件信息、按时间轴顺序切分片段、合并输出、生成字幕轨道、做转场。底层可以走 FFmpeg 也可以走 WebCodecs我在项目里做了一层渲染器抽象方便替换。第三部分是 MCP Server。它把画布和剪辑能力包装成一组标准工具供 Agent 调用。画布上发生的每一次变化都会被同步成一个可查询的项目状态Agent 通过工具接口读写这个状态而不是直接操作界面。这三件套的价值在于Agent 可以全流程操控但每一步动作都有明确的语义和返回值。它不是简单地把鼠标坐标传给 Agent 去点而是让 Agent 理解“这段 clip 是哪条素材的哪一段”“当前画布光标在第几秒”这种结构化信息。1.3 和 libTV 的核心差异我用一张表说清楚对比项libTV 类工具我这个开源方案交互入口自家产品的提示词MCP 标准工具多 Agent 通用素材存储通常在云端或产品私有空间本地文件引用不上传扩展性受限于产品功能边界可自己加工具、换渲染器部署形态在线服务为主本地进程离线可用自动化能力受 API 开放程度限制Agent 可编程编排完整剪辑流程成本模型按服务订阅或点数计费只花本地算力提示词格式厂商自定义MCP 内置 JSON Schema 描述Agent 自动理解这张表可能有些主观但核心点很明确如果你需要的只是顺手剪一条 AI 视频libTV 这类工具完全够用如果你想做一个让 Agent 自主完成“从素材到成片”的本地自动化管道MCP 方案的自由度和可控性明显更高。2. 核心架构拆解画布状态机、剪辑内核与 MCP Server 如何分工2.1 画布不是“画板”是一套可被外部读取的项目状态机项目最开始我也踩过一个典型的坑想当然地把画布做成了一块实时渲染区域然后让 Agent 通过移动鼠标去操作。结果发现这条路走不通因为 Agent 对像素级的坐标操作根本不可靠而且非常慢。后来我把画布重新定义为“项目状态的可视化呈现”核心不再是渲染而是状态。项目状态ProjectState是一个完整的 JSON 结构包含素材列表、时间轴片段、画布光标位置、当前选中的片段、字幕、转场配置等。画布界面只是把这个状态渲染出来。Agent 每次调用工具本质上是在读写这个状态机。画布左右移动的方法也从这个设计里自然浮现出来了画布光标指向时间轴上的某个时间点向左移动 5 秒、向右移动 10 秒就是一次状态更新。这个操作被封装成 move_cursor 工具而不是真的去点界面上的箭头按钮。这带来的好处是Agent 可以精确地知道移动前后光标落在哪里再配合 get_project_state 工具确认效果形成完整的操作闭环。libTV 用户常问的“画布左右移动方法”在我这里其实是一个游标移动语义而不是某个快捷键。2.2 剪辑内核时间轴数据模型要先于渲染确定剪辑内核我采用了“素材引用 时间轴片段 渲染队列”三层结构。素材引用保存的是本地文件的路径、时长、分辨率、编码格式等元信息。时间轴片段则是在素材基础上追加了入点、出点、轨道编号、特效标记。渲染队列负责把当前时间轴状态翻译成具体的合成指令。之所以强调“数据模型先于渲染”是因为 Agent 操控剪辑时永远是在和数据模型打交道。比如说 Agent 想删掉第 10 到第 15 秒的片段它调用 remove_clip 工具传入 clip_id内核负责更新时间轴数据结构。这时候画布界面会收到一个状态变更事件自动重新渲染。如果先把界面渲染做完了再去想数据就会遇到界面状态和时间轴数据不同步的难题。还有一个设计点是素材文件为什么用路径引用而不是直接拷贝到项目目录。原因是视频文件体积太大了本地做自动化处理时拷贝文件的时间和磁盘开销都不可接受。直接用路径引用配合权限校验控制访问范围能保证效率又能防止 Agent 乱读系统中的其他文件。2.3 MCP Server 的桥接层一切工具都走同一套语义MCP Server 在这里扮演的角色非常明确把剪辑内核的功能翻译成 Agent 能理解的语言。MCP 的 tools 定义基于 JSON Schema包含工具名称、描述、参数结构、返回值结构。Agent 拿到这些定义后会自动根据用户指令选择合适的工具并填入参数。这里我选了 TypeScript 来实现 MCP Server主要是生态比较省心官方 SDK 用起来也顺手。下面是一个简单的 create_project 工具注册示例import { Server } from modelcontextprotocol/sdk/server/index.js; import { CallToolRequestSchema } from modelcontextprotocol/sdk/server/mcp.js; server.registerTool( create_project, { title: 创建剪辑项目, description: 在本地画布中创建一个新的视频剪辑项目。创建成功后返回 project_id。后续所有剪辑工具都需要基于该 project_id 操作。, inputSchema: { type: object, properties: { project_name: { type: string, description: 项目名称建议使用语义化名称例如 产品介绍_0426, }, width: { type: number, description: 画布宽度默认 1920 }, height: { type: number, description: 画布高度默认 1080 }, fps: { type: number, description: 帧率默认 30 }, }, required: [project_name], }, }, async (args) { const project createProject(args); return { content: [ { type: text, text: JSON.stringify({ project_id: project.id, project_name: project.name, status: created, }), }, ], }; } );这里有一个很多人容易忽略的细节工具描述必须写清楚前置条件和副作用。我见过有人把 create_project 的描述写成“创建项目”结果 Agent 在已有项目的情况下也频繁调用反而把状态搞乱了。我在描述里加了“创建后返回 project_id。后续所有剪辑工具都需要基于该 project_id 操作”就是要让 Agent 知道这个工具应该在最开始调用并且结果会被后续步骤引用。用 MCP 而不是自研 REST API 的决定也是经过实际对比的。自研 API 的问题是每个 Agent 客户端都要单独接一遍每家的调用习惯还不一样。而 MCP 是现在各大 AI 工具的标准协议Claude Desktop、Cursor、Trae、Cline 都原生支持我只需要写一个 server所有客户端都能复用这个性价比要划算得多。3. 让 Agent 插上手MCP Server 部署与工具注册实操3.1 服务端启动本地进程优先用 stdio 模式MCP Server 有两种常见运行模式stdio 模式和 HTTP/SSE 模式。对于本地剪辑工具来说我强烈建议用 stdio 模式。原因是本地进程通过标准输入输出通信不需要监听端口不会有防火墙弹窗或端口冲突问题而且启动速度快。启动命令很简单如果你使用 npm 全局安装可以这样写npm install -g local-cut-studio local-cut-studio --mcp项目开发阶段也可以直接用仓库里的启动脚本git clone https://github.com/yourname/local-cut-studio.git cd local-cut-studio npm install npm run build node dist/cli.js --mcp启动后终端会进入 MCP 通信状态正常情况下不会有太多输出只有调用工具时才会打印日志。如果看不到任何提示不要慌这是正常的说明它在等你上游 Agent 客户端来连接。3.2 注册到各类 Agent 客户端配置文件写法不同的 Agent 客户端配置 MCP Server 的方式大同小异核心都是指向同一条命令。最常用的是 Claude Desktop 和 Cursor它们的配置文件我分别写一下。Claude Desktop 的配置文件在claude_desktop_config.json里需要加入 mcpServers 节点{ mcpServers: { local-cut-studio: { command: node, args: [/absolute/path/to/local-cut-studio/dist/cli.js, --mcp] } } }Cursor 和 Trae 这类编辑器通常在设置里的 MCP 面板中添加配置格式类似{ mcpServers: { local-cut-studio: { command: node, args: [/absolute/path/to/local-cut-studio/dist/cli.js, --mcp] } } }ClineVS Code 插件则是在它的 MCP Marketplace 设置里填同样格式的 JSON。配置好之后重启客户端让 Agent 重新扫描工具列表。有一个需要注意的点命令必须写绝对路径。很多 Agent 客户端启动的进程环境和你终端里的 shell 环境不一样用npx或者相对路径可能找不到依赖。我第一次配置失败就是因为写的是npx local-cut-studioClaude Desktop 启动后找不到这个命令。改成 node 加绝对路径脚本之后就稳定了。3.3 工具清单设计我暴露了哪 12 个核心工具给 Agent整个 MCP Server 我暴露了 12 个核心工具覆盖从创建项目到导出成片的完整链路。工具名作用关键参数create_project创建剪辑项目project_name, width, height, fpslist_projects查看本地所有项目无get_project_state获取当前项目状态project_idimport_media导入本地素材project_id, file_pathadd_clip将素材片段加入时间轴project_id, media_id, start, end, trackmove_cursor画布光标左右移动project_id, direction, secondstrim_clip裁剪指定片段project_id, clip_id, new_start, new_endremove_clip删除指定片段project_id, clip_idset_transition设置片段间转场效果project_id, clip_a, clip_b, type, durationadd_subtitle添加字幕project_id, text, start, endapply_template应用画布模板project_id, template_nameexport_video导出成片project_id, output_path, format工具数量不是越多越好而是要保证每个工具边界清晰、参数简单。我早期加过很多细分工具比如 split_clip、duplicate_clip、adjust_volume后来发现 Agent 经常混淆调用成功率反而下降了。精简到 12 个之后工具的语义一目了然Agent 几乎不需要额外提示就能正确选用。这也算是一个“少即是多”的例子。3.4 让 Agent 理解“先做什么后做什么”工具描述里的依赖引导Agent 调用工具并不是完全自由的它的行为会受到工具描述和参数描述的强烈影响。想让 Agent 不犯错关键是在描述中写清楚工具间的依赖关系和调用时机。我举一个例子。add_clip 的参数描述里我不仅写“媒体 ID”还会加一句“通过 import_media 获取导入成功后返回的 media_id”。“通过 xxx 获取”这个提示非常有用Agent 会自动先调用 import_media再调 add_clip。再比如 export_video 的描述我会写上“导出前请确保时间轴已有至少一个 clip否则会返回 ERROR_EMPTY_TIMELINE”。Agent 读到这个描述后会在导出前主动检查时间轴状态如果为空它会先添加素材再导出。这种做法实际上是在给 Agent 画了一条隐形的流程引导类似 libTV 的提示词格式约束。区别在于 libTV 的提示词格式是给用户背的而我这里是直接写成机器可读的工具描述Agent 每次调用前都会重新读取所以不需要用户去记忆。4. 从空画布到成片Agent 全流程操控一个短视频项目4.1 场景设定让 Agent 完成一段 30 秒产品介绍视频理论讲多了还是得看一次真实的调用过程。我做了一个测试场景给 Agent 一段自然语言指令让它完成一个 30 秒的产品介绍视频。素材是本地三份 MP4 文件分别是产品开箱、功能演示、用户评价。目标是让 Agent 自主完成导入、排列、裁剪、加字幕、导出。我用的指令是“帮我做一条 30 秒产品介绍视频用本地素材目录 /Users/me/videos 下的三个文件按开箱、功能、评价的顺序排列展示 3D 打印机功能演示那段要保留完整其他可以适当裁剪结尾加字幕‘智能制造从此简单’导出成 mp4 到桌面。”4.2 第一步创建项目与导入素材Agent 拿到指令后首先调用 create_project。这一步通常没有任何偏差因为描述里写清楚了返回 project_id。实际返回结果{ content: [ { type: text, text: {\project_id\:\proj_8f3k2a\,\project_name\:\3D打印机产品介绍\,\status\:\created\} } ] }拿到 project_id 后Agent 继续调用 import_media。它会读取本地的三份文件逐个导入。在 import_media 的实现里我做了文件格式校验非视频文件会返回错误这样 Agent 就能及时感知到路径不对。{ tool: import_media, arguments: { project_id: proj_8f3k2a, file_path: /Users/me/videos/unboxing.mp4 } }返回结果里包含 media_id、时长、分辨率等元信息Agent 会把这些信息记住并用于后续的 add_clip 调用。4.3 第二步画布左右移动与片段裁剪素材导入完之后Agent 开始按顺序向时间轴添加片段。这里涉及“画布左右移动方法”的实战含义Agent 并不需要直接看到画面它通过 move_cursor 工具来调整当前的焦点位置然后决定在哪一个时间点插入或裁剪片段。我按原始需求描述设计了一次有代表性的调用序列add_clip 把开箱视频 0-10 秒加入轨道add_clip 把功能演示整段加入轨道move_cursor 向右移动 10 秒找到功能演示段的起始位置trim_clip 把功能演示段裁剪到 10-22 秒add_clip 把用户评价视频加入轨道位置在 22 秒之后move_cursor 向左移动 5 秒定位到评价段开头确认片段衔接这套操作里move_cursor 并不是必须的但 Agent 在不确定时间轴位置时会主动调用它来校准。我发现这其实是 Agent 的一个很聪明的行为在没有可视化界面的情况下它通过光标位置和时间轴状态来建立对项目的空间认知。4.4 第三步Agent 自主调用转场、字幕与导出片段排列好了Agent 下一步调用了 set_transition在功能演示段和评价段之间添加了一个交叉溶解转场时长 0.5 秒。{ tool: set_transition, arguments: { project_id: proj_8f3k2a, clip_a: clip_002, clip_b: clip_003, type: crossfade, duration: 0.5 } }转场设置完成后Agent 调用 add_subtitle 在最后 3 秒添加字幕“智能制造从此简单”。字幕的开始时间和结束时间Agent 会根据时间轴总长度自动计算。这里我发现了一个值得注意的行为Agent 会先调用 get_project_state 查看时间轴长度再倒推字幕位置而不是拍脑袋填一个数字。最后一步是 export_video。Agent 指定输出路径为桌面格式 mp4。导出过程会实时返回进度信息Agent 能够在导出完成后拿到最终文件路径。整个调用链路记录如下总共 12 次工具调用全程没有人工介入create_project import_media (unboxing.mp4) import_media (feature.mp4) import_media (review.mp4) add_clip (unboxing 0-10s) add_clip (feature 0-30s) move_cursor (right 10s) trim_clip (feature 10-22s) add_clip (review 0-8s) set_transition (clip_002 - clip_003) add_subtitle (智能制造从此简单) export_video4.5 中间失败与恢复排查一次真实调用链路虽然整体流程很顺但实际跑的时候还是失败过一次。Agent 在导入素材时开箱视频文件路径里有一个空格它把路径写成/Users/me/videos/unboxing.mp4而我本地实际文件名是unboxing_final.mp4。这可能是因为 Agent 根据我的自然语言描述猜了文件名而不是真的去目录里看。import_media 返回了如下错误{ isError: true, content: [ { type: text, text: {\errorCode\:\ERROR_FILE_NOT_FOUND\,\details\:\File not found: /Users/me/videos/unboxing_final.mp4\} } ] }Agent 收到错误后立刻调用了 list_projects 之外的另一个工具 list_media_probe重新扫描目录下真实可用的文件然后修正路径再次导入。这让我确认了结构化错误返回的重要性只有错误信息足够精确Agent 才能进行自我纠偏。如果把错误包装成一串无结构的人类读文本Agent 很可能要猜测下一步该干什么。5. 踩坑实录状态同步、路径注入、Agent 乱序调用这三大坑5.1 状态同步画布界面显示和实际项目状态不一致第一个大坑是状态同步。最初版本里画布界面维护自己的 UI 状态剪辑内核维护项目状态MCP Server 读写项目状态。看似清晰的隔离实际上因为事件监听不到位会出现“界面显示片段还在实际已经被 Agent 删掉”的情况。调试了很久最后我把项目状态设计成了唯一数据源画布界面不再保存任何剪辑相关的数据只订阅状态变更事件。每次 Agent 调用工具导致状态变化都会广播一个状态变更事件画布收到后重新拉取全量状态渲染。这个决策看起来很基础但能避免一类很隐蔽的问题Agent 拿到旧状态、基于旧状态继续操作最后把新改动覆盖掉。现在所有读写都走状态机通过版本号控制并发冲突Agent 的操作就变得可预期了。5.2 文件路径注入与权限边界Agent 可以调用 import_media 导入任意路径如果完全不校验Agent 被恶意提示词诱导时有可能读取系统上不该读的文件。虽然 Agent 本体有自己的安全机制但工具端也应该有自己的边界。我做了两层防护。第一层是路径校验import_media 只允许导入媒体格式后缀的文件并且路径必须处在配置的媒体根目录下。第二层是权限注入MCP Server 启动时可以指定--allow-dir只有该目录下的文件可以被访问。local-cut-studio --mcp --allow-dir /Users/me/videos这个设计既是安全措施也是提示词工程的一部分。Agent 看到 import_media 的描述里写着“只支持 /Users/me/videos 目录下的文件”就会主动把用户的路径需求翻译成合法路径而不是绕过去找别的路径。5.3 Agent 乱序调用如何用结构化错误让 Agent 自我纠偏Agent 乱序调用是绕不开的问题。最典型的例子是Agent 还没有导入任何素材就直接调用 set_transition结果返回了“找不到片段”的错误。原因是 Agent 根据用户指令里的“加转场”优先做了转场操作忘了时间轴上还没有片段。遇到这种情况不要指望 Agent 一次就做对而是要让错误信息足够精确地指出“你漏掉了什么”。我实现的 set_transition 在找不到片段时会返回如下错误{ errorCode: ERROR_CLIP_NOT_FOUND, details: Timeline has 0 clips. Need at least 2 clips to set transition., hint: Try calling import_media then add_clip before set_transition. }hint 字段是关键。它不是给用户看的而是给 Agent 看的。Agent 拿到 hint 后会自动调整顺序重新执行导入、添加、设置转场。我把这个思路应用到了所有可能因前置条件不满足而报错的工具上。这个方案不能说百分百完美但实测下来 Agent 的自我纠偏成功率至少提升了一半。如果你也在做 MCP 工具强烈建议所有错误返回都带上 hint。5.4 性能优化大视频素材带来的卡顿和超时本地剪辑最现实的性能问题是 FFmpeg 处理大文件时的耗时。Agent 调用 export_video 后如果直接同步等待导出完成MCP Server 的进程会被长时间占住。如果 Agent 客户端还有超时限制就会误报失败。我的解决方案是引入异步任务机制。export_video 立即返回一个 task_id后续 Agent 可以调用 task_status 查询进度。这样既不会阻塞服务进程也让 Agent 能够更优雅地等待任务完成。渲染队列也做了并发控制同一时间只跑一个导出任务避免多个任务争抢 CPU 导致导出失败。为了提高缩略图和预览图的响应速度我给导入的素材做了 probe 缓存。第一次导入时解析媒体元信息存到本地 SQLite 里后续就不用反复执行 FFprobe。这个优化对于几十个素材的项目效果非常明显导入速度能快一倍以上。还有一个容易被忽视的坑内存。多个大视频同时被解码时内存占用可能轻松超过 2GB。我在测试中把渲染队列改为串行之后内存稳定在 800MB 以内体验提升了一个档次。6. 项目的边界与后续扩展它能做什么、不能做什么这个项目目前能覆盖的基本就是单轨时间轴的剪辑流程导入素材、按时间轴排列、裁剪、转场、字幕、导出。对于日常短视频、产品介绍、口播视频的自动化生成来说已经够用了。我实际把这套东西跑在每周固定生成工作复盘视频的流程里效果稳定省下了不少手动剪辑时间。但我也不会回避它的不足。多轨视频叠加、画中画、关键帧动画、音频 EQ 调音这类复杂功能当前版本并没有实现。如果你拿它当 Premiere 的替代品肯定会失望。它的定位不是“又一个大而全的剪辑软件”而是“一个能被 Agent 顺畅操控的本地剪辑工具”。后续有几个方向我认为价值比较大第一是支持多轨时间轴让 Agent 能编排画中画和字幕轨道第二是把渲染器插件化允许接入不同的渲染后端第三是做一个画布实时预览窗口的 WebSocket 推流让用户能看到 Agent 剪辑的每一帧变化。如果你想参与开源贡献可以从这三个方向选一个入手。代码结构和工具注册方式在仓库 README 里有说明MCP Server 部分很好定位因为所有工具入口都集中在一个 tools 目录下新增一个工具只需要写一个注册函数加一个处理函数即可。最后分享一个小经验当你设计 Agent 可操控的工具时要像设计 API 一样克制。每一个工具都必须是不可再分的原子操作并且描述里必须写清前置条件和返回值。Agent 是一个极其聪明但偶尔短路的使用者你给它的信息越结构化它发挥得就越稳定。这个项目的核心价值不在于我写了多少行代码而在于把“AI 自动剪视频”从 demo 变成了一个可以跑在本地、可以被任意 MCP 客户端调用的工程化方案。你可以把它理解成一个基础设施至于 Agent 能用它拍出什么样的片子那就是上层应用的事了。