upstash-box-remote-work について
UpstashのリモートクラウドBoxで開発作業を進めるワークフロースキルです。分離されたLinux環境、プルリクエスト、公開プレビュー、スクリーンショットが必要な作業向けに、BoxやGit、実行、後片付けの手順を説明します。
主な機能
- 01クラウドサンドボックスBoxの作成と管理
- 02リモートでのシェルコマンドとGit操作
- 03ポート経由のプレビューURL公開
- 04ブラウザー画面の撮影とBlobへのアップロード
- 05スナップショットと並列Boxによる独立タスクの拡張
活用シーン
- 01生成コードや未信頼コードの隔離環境での構築とテスト
- 02公開プレビュー付きプルリクエストの作成
- 03複数の独立した開発タスクの並列実行
MCP 対応
このワークフローは https://mcp.upstash.com/mcp のUpstashリモートMCPサーバーを介して動作します。ツールでBox管理、シェルやGit操作、プレビュー作成、ブラウザーセッション、スナップショット、Blobへのアップロードを行います。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ upstash/ skills --skill upstash-box-remote-workインストール後、Claude Code で/upstash-box-remote-workを入力して読み込まれたか確認します。
詳細
ボックスは、シェル、ファイルシステム、git、オプションのヘッドレス Chromium、およびそのポート用の公開 URL を備えた、クラウド上のサンドボックス化された Linux コンテナです。ここでのすべては、リモートの Upstash MCP サーバーを経由します。インストールする SDK も、環境内の API キーもありません(動画の録画は例外です。以下を参照してください)。サーバーはセッションの OAuth トークンを Box API に転送し、スクリーンショットのバイト列はサーバーを経由せずにボックスから Blob へと送られます。
いつ作業をボックスに持ち込むか
- ユーザーがそれを求めている場合: リモートで、サンドボックスで、クラウドで、ボックスで、自分のマシンではなく。
- 成果物がプルリクエスト、公開プレビュー URL、または実行結果のスクリーンショットである場合。ボックスには GitHub の認証情報、公開ポート、ブラウザーがあり、ローカルマシンにはしばしばその 3 つのどれもありません。
- 作業に分離が必要な場合: 信頼できないコードや生成されたコード、重い依存関係のインストール、クリーンなチェックアウト、ローカルのツリーが抱えるべきでないブランチ。
- 作業をスケールアウトする場合: 複数の独立したタスクを、それぞれ 1 つのボックスで、並行して。
タスクがボックスに入ったら、そのすべてをそこで行ってください。ボックスのファイルシステムはローカルのものではないため、ローカルで行った編集とボックスで実行したビルドは 2 つの異なるチェックアウトに作用し、どちらの側も不一致を報告しません。
接続
プラグインはすでに https://mcp.upstash.com/mcp を登録しており、ボックスと blob のツールはそのデフォルトのツールセットに含まれています。サーバーを手動で追加するには:
claude mcp add --scope user --transport http upstash "https://mcp.upstash.com/mcp"
初回使用時に、クライアントが Upstash の同意ページを開きます。ボックスとバケットを配置するアカウントまたはチームを選び、読み取り専用スイッチをオフにしてください: 一覧表示を除く以下のすべてのステップは、読み取り専用の許可では 403 で拒否されます。
ツール
| ツール | アクション / 目的 |
|---|---|
box_manage | create、list、get、delete、pause、resume、fork |
box_exec | ボックスでシェルコマンドを実行します(command は argv 配列、folder は作業ディレクトリです) |
box_git | clone、status、diff、commit、checkout、push、create_pr |
box_preview | ボックス内のポート用の公開 URL を create、list、delete |
box_browser | goto、content、screenshot、tabs、tab_new、tab_close、live_view |
box_snapshots | create、list、list_all、delete、restore(スナップショットからの新しいボックス) |
box_logs、box_runs | ボックス内で何が起きたか、およびその実行履歴 |
box_apikey | デプロイされたアプリまたは CI 用の Box API キーを list、create、delete(キーは OAuth の許可より長く存続するため、完了したら取り消すようユーザーに伝えてください) |
blob_bucket | list、create(create のデフォルトは visibility: public) |
blob_upload_url | バケット内のパス用の署名付き PUT URL、および公開バケットでの public_url |
フロー:1 タスク、1 ボックス
- 作成。
box_manageのcreateです。スクリーンショットを撮ったりページを確認したりする場合はbrowser: trueを設定します。使い捨ての作業にはttlとともにephemeral: trueを使用します(有料プランは不要です)。プレビュー URL をセッションより長く存続させる必要がある場合はkeep_alive: trueを使用します(有料プラン)。返されたidを控えておきます。 box_gitのcloneでクローンします。box_execでgitを使うことは決してしないでください。クローンこそがアカウントの GitHub 認証情報をボックスに書き込む処理であり、これを行わないとpushやcreate_prが素っ気ない 500 で失敗します。チェックアウト先は/workspace/home/<リポジトリ名>になります。以降のすべてのbox_execとbox_git呼び出しでは、これをfolderとして渡してください。デフォルトはワークスペースのルートであり、リポジトリではありません。- 作業。 インストール、ビルド、テスト、アプリ自体には
box_execを使用します。各呼び出しはコマンドの完了を待つため、サーバーはデタッチします:["sh", "-c", "( pnpm preview --host 0.0.0.0 --port 4321 > /workspace/home/app.log 2>&1 & )"]、 その後、2 回目の呼び出しでcurlを使ってポートをポーリングします。ファイルはローカルのファイルツールではなく、box_exec内のシェルコマンドまたは短いスクリプトで編集してください。 - プレビュー URL。
box_previewのcreateをportとともに実行します。アプリは0.0.0.0でリッスンする必要があります。127.0.0.1にバインドされたサーバーはボックス内の curl には応答しますが、プレビュー経由では 502 を返します。URL はhttps://<box-id>-<port>.preview.box.upstash.comという形式です。リンクを知っている誰にでもページを開放したくない場合はbasic_authまたはbearer_tokenを追加します。認証情報は一度だけ返されます。URL がどれだけ存続するかを返信で伝えてください。エフェメラルボックスではexpires_atに URL が停止します。 - スクリーンショット。
box_browserのgotoでhttp://localhost:<port>のページに移動し、次にscreenshotを実行します。pathを指定しない場合、PNG は確認できる画像として返されます。pathを指定した場合はボックス内に書き込まれ、{saved, bytes}だけが返されます。作業中に確認し、残すべきものは保存し、長いページにはfull_page: trueを渡します。 - スクリーンショットの公開。 GitHub の添付エンドポイントはボックスのトークンを拒否するため、画像は Blob を経由します。
blob_bucketのlistを実行し、合うものがなければ public のバケットをcreateします(プライベートバケットは短命の署名付き URL しか提供しません)。bucket_idとfiles: [{path: "<repo>/<branch>/after.png", content_type: "image/png", size: <bytes>}]を指定してblob_upload_urlを、使用直前に発行します(URL の有効期間は最大 10 分です)。次に、結果のcurl_exampleをbox_execで実行し、返されたヘッダーをそのまま送信します(それらは署名の一部です)。バイト列はボックスからストレージへ直接送られます。 - プルリクエスト。
box_gitのcheckoutでブランチを作成し、commit、ブランチ名を指定してpushし、次にbase、title、そしてプレビュー URL と各スクリーンショットのを含むbodyを指定してcreate_prを実行します。create_pr自体は何もプッシュしません。PR の URL、プレビュー URL、スクリーンショットの URL を返信で伝えてください。 - クリーンアップ。 ユーザーがボックスやそのプレビューの保持を希望しない限り、
box_manageのdelete(またはpause)を実行します。エフェメラルボックスは自動的に期限切れになります。
スケールアウト
- 独立したタスクごとに 1 つのボックス。共有の
labelsエントリで作成し、並行して操作し、最後にlabelを指定してbox_manageのlistで見つけて削除します。1 つのボックスで同時に 2 つのタスクを実行しないでください。 - クライアントにサブエージェントがある場合は、それぞれに専用のボックスを与えて並行して実行します。ボックスを共有するエージェントは互いのファイルとプロセスを上書きしてしまいます。
- すべてのタスクが同じ高コストなセットアップ(クローン、依存関係のインストール、ビルドキャッシュ)を必要とする場合は、一度だけ実行し、
box_snapshotsのcreateを行い、次にスナップショットからタスクごとに新しいボックスを 1 つrestoreします。forkはアイドル状態または一時停止中の非エフェメラルボックスに対して同じことを行います。 sizeはsmall、medium、largeです。すべてを過大に指定するのではなく、タスクごとに選んでください。live_viewURL を使うと、人がボックスのブラウザタブが動作する様子を観察できます(フレームは出力されますが、入力はできません)。長時間の実行ではこれを渡してください。
ボックスでの動画録画
録画はまだ MCP ツールではないため、これが SDK を必要とする唯一のフローです。
box_apikey の create でキーを作成し、それを box 内のファイルに保管し、
そこから @upstash/box で box を操作し(box.browser.recordings、および
Playwright 用の box.browser.cdpUrl())、完了したらキーを削除します。
何が録画されるか。 box のブラウザです。すべてのタブを、フォアグラウンドの
タブに追従しながら、固定解像度で録画します。それ以外はブラウザに入れる必要が
あります。ターミナルプログラムは ttyd(GitHub リリースの静的バイナリ。
Debian にはパッケージがありません)を介して、固定サイズの tmux セッションに
接続します。入力は tmux send-keys -l で小さなチャンクに分けて送信して
ください。TUI はブラケットペーストを潰してしまうためです。tmux set -g status off
にしないと、ステータスバーがすべてのフレームに映り込みます。可能な限り
1 つのタブにとどまってください。新しく開かれたタブは確実には追従されません。
box 内でのエージェントや CLI の実行。 手動で起動した CLI エージェントには
独自のモデルキーが必要です。box の管理キーは、Box ランナーが起動するプロンプト
にしか届きません。チャットに貼り付けずにキーを box に入れるには、box から
ワンショットのフォームを提供します(1 回の POST を受け付ける小さな HTTP
サーバー)。box_preview で公開し、ユーザーにリンクを送ると、そのフォームが
値をファイルに追記して終了します。localhost の OAuth コールバックを待つ
ログインはトンネルできません(Box SSH は -L を拒否します)。CLI が
リダイレクト URI を設定できる場合(OpenCode: opencode.json の
oauth.redirect_uri)、Box のプレビュー URL を指すようにします。コールバック
サーバーは依然として URI のポート(https の場合は 443、非特権で許可されます)
で 127.0.0.1 にバインドし、プレビューは 0.0.0.0 にしか到達しないため、付与の
間だけプレビューポートをそこにリレーします。そうでなければ、box 内でログインを
実行し、ユーザーが自分のブラウザで承認し、CLI が待機をやめる前に、失敗した
http://127.0.0.1:<port>/... URL を box 内の curl に貼り付けてもらいます。
同意画面でアカウントが選ばれるため、録画が依存するリソースはそのアカウントに
存在している必要があります。無人でのエージェント実行では、明確化の質問を
排除するワークスペース指示があれば、テイクが停滞するのを防げます。
制限。 録画は最長 600 秒で、ピクセルの変化が 3 分間ないと自動的に停止します。
1 つの box は 1 つのアクティブな録画を保持します。残った録画があると、次の
start が 409 を返すため、クラッシュハンドラー内および毎回の start の前に
停止してください。加速する予定の待機も含め、見せたいものはすべて録画して
ください。ギャップを後から編集で戻すことはできません。
タイミング。 目分量ではなくタイムスタンプから切り出します。スクリプトが
実行するすべてのアクションの実時間をログに記録し、録画されたプログラム自身の
タイムラインは画面からではなく構造化出力(セッションのエクスポート、ログ、
API レスポンス)から取得し、両方をビデオ時間に変換します。開始ではなく終了を
基準にします。キャプチャは startedAt の約 2 秒後に始まるため、最初のフレームは
endedAt - <file duration>(ffprobe)であり、startedAt を使うとすべてのカットが
早くなります。ターミナルからスクレイピングしたテキストは行の折り返しで途切れます。
大量の CLI 出力はファイルにリダイレクトしてください。パイプはそれを切り詰める
ことがあります。
編集(ffmpeg):
- 長い待ち時間はカットするのではなく高速化します。ばっさりカットすると
不具合のように見えます。丸い係数(5、10、20、50 倍)を使い、セグメントの
長さはその係数から決まるようにし、係数は画面上の隅に濃い文字で
不透明な緑のピル(
box=1:boxcolor=0x00d48a)として表示します。半透明の バッジではいけません。どんな背景の上でも一目で読める必要があります。 - 何が起きているかを画面上のテキストで示します。各ステップに短いキャプションを 付け、読めるだけの長さ(およそ 0.7 秒以上)表示し、ステップの進行が速いときは 間引きます。
- ヘッドレス録画にはポインターもアドレスバーもありません。入力が行われる場所に
カーソルとクリックマーカーを描画し、URL が重要な場合は URL を描画します。
位置は Playwright のバウンディングボックスから取得します(まず要素を
ビュー内にスクロールしてください)。ターミナルの場合は ttyd の
window.termから取得します。セルサイズは.xterm-screenの矩形をcolsとrowsで 割ったもので、行と列はterm.buffer.activeを検索して求めます。 - カーソルは手で動かしたように見せます。小さく細いアンチエイリアス済みの矢印 (1280x800 で高さ約 25 px)に柔らかい影を付けます。各移動はなめらかに 加減速し(smootherstep)、わずかな曲線(制御点を横に押し出した二次ベジェ) に沿わせ、クリックの少し前に到着させ、停止中はかすかな揺らぎを加えます。 クリックは短い押し込み(約 0.15 秒だけ少し小さくなるスプライト)と、約 0.4 秒かけて広がって消える細い波紋で示します。塗りつぶしたリングでは いけません。各セグメントの境界ではカーソルを保持し、次のセグメントの位置へ 滑っていかないようにします。
- 波紋は PNG シーケンスとしてレンダリングし、クリックごとに 1 つのコピーを
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 ツールで録画したエージェントは、何かを作成する前に 既存のものを一覧表示します。前のテイクが残した box があると、クローンを スキップしてその box を再利用します。先に残置物を削除するか、プロンプトに 「新しい box」と書いてください。録画されたエージェントが作成する box は、 そのエージェントが同意したアカウント内に存在し、それがあなた自身のセッション から見えるアカウントとは限りません。
複数の録画。 録画ごとに 1 つの box を使い、クライアントにサブエージェントが ある場合はそれらで並列に操作します。録画用の box をエージェント間で共有しては いけません。tmux サーバー、ブラウザー、録画スロットは box ごとに独立しており、 2 つのエージェントは互いのセッションを黙って殺し、互いのファイルを上書きします。
注意点
- 読み取り専用の付与 → 作成、実行、パスへのスクリーンショット、git への書き込み、アップロード URL で 403 になります。読み取り専用をオフにして再同意してください。
box_gitのcreate_prは、アカウントが対象リポジトリをカバーする GitHub インストールを持っている場合に失敗します。Upstash コンソールの Box 設定で GitHub を接続するよう依頼してください。box_manageのcreateではclone_reproよりもbox_gitのcloneを優先してください。エフェメラルな box ではそのオプションは無効にでき、永続的な box ではバックグラウンドで実行され、box_logs以外に完了シグナルはありません。- box 内のファイルは絶対パスでない限り
workspace/homeからの相対パスになります。ls/workspaceは root 所有、モード 711 です。 nodeイメージには corepack がなく、box ではnpm i -gができないため、packageManagerで固定された pnpm は自己インストールに失敗します。npx -y pnpm@<version>を使うか、export npm_config_manage_package_versions=falseを設定してください(自己インストール型の pnpm)。sudo npm i -g pnpm@<version>も動作します。sudo はパスワードなしです。blob_upload_urlのヘッダーは重要です。content-typeやcache-controlを変更するとストレージで失敗します(SignatureDoesNotMatch)。期限切れの URL は同様に失敗します。- バケットはアカウント全体で共有されます。リポジトリごとに作成するのではなく、
agent-proofのような 1 つを再利用してください。 box_bは box がbrowser: trueで作成されていない限り「browser is not enabled for this box」で失敗します。後から有効にする方法はありません。- パブリックな Blob ホストはオブジェクトをキャッシュします(
cache-controlヘッダーに従います)。同じパスに再アップロードしても古いオブジェクトが配信され続けます。置き換える場合は新しい名前で公開し、上書きしたばかりのものを読み戻すときはクエリ文字列を追加してください。 box_execのリクエストは 60 秒でタイムアウトします。長時間のジョブはsetsid ... &でデタッチし、後続の呼び出しでポーリングし、プロセスがまだ生きているか確認してください。box_execのpkill -f <pattern>は呼び出し元シェル自身のコマンドラインにも一致して呼び出しを kill します(exit 143)。pkill -f 'patter[n]'と書いてください。tmux kill-serverの直後にtmux new-sessionを実行すると「server exited unexpectedly」で失敗することがあります。先に古いサーバーの終了を待ってください。nodeイメージには PIL も ImageMagick もありません。必要なものをsudo apt-get installするか、Python のzlibで小さな PNG を書き出してください。