关于 upstash-box-remote-work
这是一项通过 Upstash 远程云端 Box 执行开发任务的工作流技能,适用于需要隔离 Linux 环境、提交 PR、公开预览或截图的工作,并说明 Box、Git、命令执行和清理流程。
主要功能
- 01创建和管理云端沙箱 Box
- 02远程运行 shell 命令和 Git 操作
- 03发布基于端口的预览 URL
- 04截取浏览器画面并上传到 Blob
- 05通过快照和并行 Box 扩展独立任务
适用场景
- 01在隔离环境中构建和测试生成或不可信代码
- 02创建带公开预览的拉取请求
- 03并行运行多个相互独立的开发任务
MCP 相关
该工作流通过 https://mcp.upstash.com/mcp 上的 Upstash 远程 MCP 服务运行。其工具用于管理 Box、执行 shell 和 Git 操作、创建预览、控制浏览器会话、管理快照及上传到 Blob。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ upstash/ skills --skill upstash-box-remote-work安装后在 Claude Code 中输入/upstash-box-remote-work确认已加载。
详细介绍
Box 是云端经过沙盒隔离的 Linux 容器,配有 shell、文件系统、git、可选的无头 Chromium,以及指向其端口的公开 URL。这里的所有操作都通过远程 Upstash MCP 服务进行。无需安装 SDK,环境中也没有 API 密钥(录制视频是例外,见下文):服务会将会话的 OAuth 令牌转发给 Box API,而截图字节会直接从 Box 传到 Blob,不经过服务。
何时将工作放入 Box
- 用户提出要求:远程、在沙盒中、在云端、在 Box 中,而不是在我的机器上。
- 交付内容是拉取请求、公开预览 URL,或运行结果的截图。Box 配有 GitHub 凭据、公开端口和浏览器;本地机器通常三者都不具备。
- 工作需要隔离:不受信任或生成的代码、繁重的依赖安装、干净的检出,或本地工作树不应包含的分支。
- 工作需要扩展:多个相互独立的任务,每个任务使用一个 Box,并行执行。
任务一旦放入 Box,就应在那里完成全部工作。Box 的文件系统与本地文件系统不同,因此在本地进行编辑、在 Box 中运行构建会作用于两个不同的检出,而且双方都不会报告这种不一致。
连接
插件已注册 https://mcp.upstash.com/mcp,Box 和 Blob 工具也属于其默认工具集。手动添加服务:
claude mcp add --scope user --transport http upstash "https://mcp.upstash.com/mcp"
首次使用时,客户端会打开 Upstash 授权页面。选择 Box 和存储桶应归属的账户或团队,并关闭只读开关:只读授权下,除列出之外的以下所有步骤都会被拒绝,并返回 403。
工具
| 工具 | 操作/用途 |
|---|---|
box_manage | 创建、列出、获取、删除、暂停、恢复、分叉 |
box_exec | 在 Box 中运行 shell 命令(command 是 argv 数组,folder 是工作目录) |
box_git | 克隆、查看状态、查看差异、提交、检出、推送、创建 PR |
box_preview | 为 Box 中的端口创建、列出、删除公开 URL |
box_browser | goto、content、screenshot、tabs、tab_new、tab_close、live_view |
box_snapshots | 创建、列出、全部列出、删除、恢复(根据快照创建一个新 Box) |
box_logs、box_runs | 查看 Box 内发生的情况及其运行历史 |
box_apikey | 为已部署的应用或 CI 列出、创建、删除 Box API 密钥(密钥的有效期会超过 OAuth 授权,因此完成后请告知用户撤销密钥) |
blob_bucket | 列出、创建(创建时默认使用 visibility: public) |
blob_upload_url | 获取存储桶中路径的预签名 PUT URL;对于公开存储桶,还会提供 public_url |
流程:一项任务,一个 box
- 创建。 使用
box_manage的create。如果要截屏或检查页面,请设置browser: true。对于临时工作,使用带有ttl的ephemeral: true(无需付费套餐);如果预览 URL 必须在会话结束后继续有效,则使用keep_alive: true(付费套餐)。记下返回的id。 - 使用
box_git的clone克隆,绝不要在box_exec中使用git。克隆操作会将账户的 GitHub 凭据写入 box;没有这一步,push和create_pr会因裸 500 错误而失败。检出目录位于/workspace/home/<repo name>。之后每次调用box_exec和box_git时,都要将该路径作为folder传入:默认路径是工作区根目录,并非仓库。 - 工作。 使用
box_exec执行安装、构建、测试和运行应用。每次调用都会等待命令结束,因此要将服务器放到后台运行:["sh", "-c", "( pnpm preview --host 0.0.0.0 --port 4321 > /workspace/home/app.log 2>&1 & )"], 然后在第二次调用中用curl轮询端口。使用 shell 命令或box_exec中的简短脚本编辑文件,不要使用本地文件工具。 - 预览 URL。 使用
box_preview的create并传入port。应用必须监听0.0.0.0;绑定到127.0.0.1的服务器虽然能在 box 内响应curl,但通过预览访问仍会返回 502。URL 格式为https://<box-id>-<port>.preview.box.upstash.com。如果不希望任何拿到链接的人都能打开页面,请添加basic_auth或bearer_token;凭据只会返回一次。回复中说明 URL 的有效时长:临时 box 会在expires_at时将其关闭。 - 截屏。 使用
box_browser的goto打开http://localhost:<port>上的页面,然后调用screenshot。不传path时,PNG 会作为图片返回,供你查看;传入path时,图片会写入 box,且只返回{saved, bytes}。工作过程中查看截图,保存重要截图;对于长页面,传入full_page: true。 - 发布截图。 GitHub 的附件接口会拒绝 box 的令牌,因此图片要通过 Blob 上传。使用
blob_bucket的list,如果没有合适的存储桶,就创建一个公开存储桶(私有存储桶只提供短期签名 URL)。传入bucket_id和files: [{path: "<repo>/<branch>/after.png", content_type: "image/png", size: <bytes>}]调用blob_upload_url,并在使用前立即生成(URL 的有效时间最长为 10 分钟)。然后在box_exec中执行结果里的curl_example,并逐字发送返回的标头(它们是签名的一部分)。字节会直接从 box 传到存储。 - 拉取请求。 使用
box_git的checkout切换到一个分支,执行commit,传入分支名称执行push,然后使用base、title和body调用create_pr;body中要包含预览 URL,以及每张截图对应的。create_pr本身不会推送任何内容。回复中提供 PR URL、预览 URL 和截图 URL。 - 清理。 除非用户希望保留 box 或其预览,否则使用
box_manage的delete(或pause)。临时 box 会自动过期。
扩展规模
- 每项独立任务使用一个 box。创建时使用共享的
labels条目,并行运行任务,最后使用box_manage的list并传入label来查找并删除这些 box。绝不要在同一个 box 中同时运行两项任务。 - 如果客户端支持子 Agent,请为每个子 Agent 分配自己的 box 并行运行;共用一个 box 的 Agent 会覆盖彼此的文件和进程。
- 如果每项任务都需要相同的耗时设置(克隆、安装依赖、构建缓存),请只执行一次,然后使用
box_snapshots的create创建快照,再从该快照为每项任务restore一个新 box。对于空闲或已暂停的非临时 box,也可以使用fork执行相同操作。 size可设为small、medium或large;应根据任务选择,而不是一律选用更大的规格。live_viewURL 可让用户实时查看 box 中正在工作的浏览器标签页(只传出画面,不接收输入);长时间运行时可将其分享给用户。
在 box 中录制视频
录制功能目前还不是 MCP 工具,因此这是唯一需要使用 SDK 的流程。
使用 box_apikey create 创建密钥,将其保存在 Box 内的一个文件中,
然后通过 @upstash/box(box.browser.recordings,以及供 Playwright
使用的 box.browser.cdpUrl())操作 Box,并在完成后删除密钥。
录制内容。 Box 浏览器:固定分辨率下的所有标签页,并跟随前台标签页。
其他内容必须放进浏览器中。终端程序通过 ttyd 运行(从 GitHub
releases 获取静态二进制文件;Debian 没有对应的软件包),并连接到固定大小的
tmux 会话;分小段使用 tmux send-keys -l 发送输入,因为 TUI 会折叠带括号的粘贴内容;
使用 tmux set -g status off,否则状态栏会出现在每一帧中。尽量留在同一个标签页中:
新打开的标签页不一定会被跟随。
在 Box 中运行 Agent 或 CLI。 手动启动的 CLI Agent 需要自己的模型密钥;
Box 的托管密钥只能用于 Box 运行器启动的提示词。不要在聊天中粘贴密钥,而要将密钥传入
Box:从 Box 启动一个一次性表单(一个只接受一次 POST 的小型 HTTP 服务),通过 box_preview
公开该服务,将链接发给用户;表单会将值追加到文件中,然后退出。等待 localhost OAuth 回调的登录流程无法通过隧道转发(Box SSH 不允许
-L)。如果 CLI 允许设置重定向 URI(OpenCode:在 opencode.json 中设置
oauth.redirect_uri),则将其指向 Box 预览 URL:回调服务仍会在该 URI 的端口上绑定 127.0.0.1(https 使用 443,可由非特权用户使用),而预览只能访问 0.0.0.0,因此在授权期间将预览端口转发到该地址。否则,在 Box 中运行登录流程,让用户在自己的浏览器中批准,然后让用户将失败的
http://127.0.0.1:<port>/... URL 粘贴回来,并在 CLI 停止等待之前,在 Box 中通过 curl 访问该 URL。授权页面会选择账号,因此录制所依赖的资源必须存在于该账号中。对于无人值守的 Agent 运行,排除澄清问题的工作区指令有助于避免录制中途停滞。
限制。 录制时长最多为 600 s;如果 3 分钟内像素没有变化,录制会自动停止。一个 Box 只能有一个正在进行的录制;残留的录制会导致下一次 start 返回 409,因此要在崩溃处理程序中以及每次开始录制前停止录制。录下所有打算展示的内容,包括计划加速播放的等待过程;缺失的片段无法事后补回。
时间。 根据时间戳剪辑,而不是凭肉眼判断。记录脚本每项操作的墙上时钟时间;从结构化输出(会话导出、日志、API 响应)中获取录制程序自身的时间线,而不是从屏幕上读取,并将两者转换为视频时间。以结束时间为基准,而不是开始时间:录制会在 startedAt 约 2 s 后开始,因此第一帧的时间是
endedAt - <file duration>(ffprobe),而 startedAt 会使每个剪辑点都提前。终端中抓取的文本会在自动换行处断开。将大型 CLI 输出重定向到文件;管道可能会截断输出。
编辑(ffmpeg):
- 加速漫长等待,而不是将其删掉;硬切会让人觉得画面卡顿。使用整数倍数(5、10、20、50x),片段时长随倍数调整,并在角落显示倍数:使用纯绿色圆角胶囊,搭配深色文字(
box=1:boxcolor=0x00d48a),不要使用半透明徽章:无论背景是什么,都必须一眼就能看清。 - 用屏幕文字说明正在发生的事:每个步骤配一条简短字幕,显示时间要足够长,便于阅读(约 0.7 s 或更长);步骤节奏较快时,减少字幕数量。
- 无头录制没有指针和地址栏。发生输入时,绘制光标和点击标记;地址栏内容很重要时,绘制 URL。位置从 Playwright 边界框获取(先将元素滚动到视野内);如果是终端,则使用 ttyd 的
window.term:单元格大小为.xterm-screen矩形区域除以cols和rows,行和列通过搜索term.buffer.active获取。 - 让光标看起来像手动移动的。使用小巧、纤细、经过抗锯齿处理的箭头(在 1280x800 画面中约高 25 px),并添加柔和阴影。每次移动都要沿着轻微弧线(控制点向侧面偏移的二次贝塞尔曲线)平滑地加速和减速(smootherstep),在点击前稍作停顿,并在停留时添加轻微漂移。点击时显示短暂按压效果(精灵略微缩小,持续约 0.15 s),并添加一道纤细的波纹,在约 0.4 s 内扩散并淡出,而不是显示实心圆环。在每个片段边界处保持光标不动,避免其滑向下一片段的位置。
- 将波纹渲染为 PNG 序列,并使用
tpad为每次点击延迟一份副本。将滤镜图写入-filter_complex_script;引号表达式中的逗号仍需写成\,(下方示例未转义)。
[0:v]trim=T0:T1,setpts=(PTS-STARTPTS)/20,fps=30,
drawtext=text='20x':fontcolor=0x062b1f:box=1:boxcolor=0x00d48a:boxborderw=10:x=w-tw-30:y=26,
drawtext=text='<step>':enable='between(t,2.0,2.8)'[fast]
[ripple]format=rgba,tpad=start_duration=<click t>:color=0x00000000[r]
[v][r]overlay=x=<cx-36>:y=<cy-36>:eof_action=pass[v2]
[v2][cursor]overlay=eval=frame:enable='between(t,A,B)':
x='st(0,clip((t-T0)/D,0,1));st(1,ld(0)^3*(ld(0)*(6*ld(0)-15)+10));
(1-ld(1))^2*X0+2*(1-ld(1))*ld(1)*XC+ld(1)^2*X1':y='...'
使用 ffmpeg ... tile=3x3 联系表检查剪辑,通过 python3 -m http.server 提供并在 box_browser 中打开;不要在录制进行时检查(这是同一个浏览器)。使用 blob_upload_url 发布视频。
两次录制之间。 使用 box 工具录制的 Agent 会在创建任何内容前列出已有内容:如果上一次录制留下了 box,它就会跳过克隆并复用该 box。请先删除残留内容,或在提示词中注明“一个全新的 box”。录制 Agent 创建的 box 属于它授权访问的账户,不一定是你自己的会话可以访问的账户。
多次录制。 每次录制使用一个 box;如果客户端支持,则由子 Agent 并行驱动。切勿让多个 Agent 共用一个录制 box:tmux 服务、浏览器和录制槽位都按 box 隔离;多个 Agent 共用时,会在不知情的情况下终止彼此的会话并覆盖彼此的文件。
注意事项
- 只读授权 → 创建、执行、截图到路径、Git 写入和上传 URL 均返回 403。关闭只读后重新授权。
- 如果账号没有覆盖该仓库的 GitHub 安装,
box_gitcreate_pr会失败;请让用户在 Upstash 控制台的 Box 设置中连接 GitHub。 - 在
box_managecreate时,优先使用box_gitclone,而不是clone_repo:对于临时 Box,该选项可能会被忽略;对于持久 Box,该操作会在后台运行,除了box_logs外没有完成信号。 - Box 内的路径默认相对于
/workspace/home,除非使用绝对路径。ls /workspace本身会被拒绝(目录由 root 所有,权限为 711)。 node镜像没有 corepack,且 boxuser 无法运行npm i -g,因此固定了packageManager的 pnpm 无法自行安装。请使用npx -y pnpm@<version>或export npm_config_manage_package_manager_versions=false(使用预装的 pnpm);也可以使用sudo npm i -g pnpm@<version>,sudo 无需密码。blob_upload_url的标头已签名。缺少或更改content-type或cache-control→ 存储服务返回 403(SignatureDoesNotMatch)。URL 过期 → 重新生成。- Bucket 名称在整个账号范围内唯一。请重复使用一个 Bucket,例如
agent-proof,并为不同仓库使用不同前缀,而不要每次运行都创建一个。 - 如果创建 Box 时没有设置
browser: true,box_browser会报错“browser is not enabled for this box”;之后无法再添加。 - 公共 Blob 主机会将对象缓存一小时(由上传时的
cache-control设置)。重新上传到同一路径仍会提供旧字节:请使用新名称发布替代文件,并在读取刚覆盖的内容时添加查询字符串。 box_exec请求会在 60 s 后超时。使用setsid ... &将长时间运行的任务放到后台,在后续调用中轮询,并检查进程是否仍在运行。- 在
box_exec中运行pkill -f <pattern>也会匹配调用它的 shell 命令行并终止该调用(退出码 143)。请改为运行pkill -f 'patter[n]'。 - 先运行
tmux kill-server,再立即运行tmux new-session,可能会失败并提示“server exited unexpectedly”;请先等待旧服务器退出。 node镜像没有 PIL 或 ImageMagick。请使用sudo apt-get install安装所需工具,或者用 Python 的zlib编写小型 PNG 文件。