Claude Code 用の stdio MCP サーバー。Zenn 記事 Claude Code と MCP で作る、社内アプリの「公開ボタン」 で紹介されている Sandbox MCP をローカル環境で再現したもの。
「アプリを書いて publish」で、ローカルの Docker コンテナとしてアプリが立ち上がります。
記事の元構成(Cloud Run / Firestore / Cloudflare / 独自 Git サーバー)を、
ローカル代替(Docker / bare git repo / ~/.sandbox/state.json / node-cron)で置き換えています。
10 個のツールを Claude Code に提供します。
| ツール | 引数 | 概要 |
|---|---|---|
sandbox_init_repo |
app_name |
作業ツリー (~/.sandbox/apps/{nick}/{app}/) と bare repo (~/.sandbox/git/{nick}/{app}.git) を作る |
sandbox_write_file |
app_name, path, content, mode? |
作業ツリー内にファイル書込み (overwrite / append) |
sandbox_read_file |
app_name, path |
作業ツリー内のファイル読取 |
sandbox_list_files |
app_name |
作業ツリーを再帰列挙 (node_modules, .git, .venv 除く) |
sandbox_list |
– | 自分の nickname のアプリ一覧 (status / URL / lastDeploy) |
sandbox_publish |
app_name, description |
非同期: Dockerfile 自動生成 → build → 既存コンテナ stop/rm → 新コンテナ起動 |
sandbox_deploy_status |
app_name |
デプロイ状況 (status, port, url, lastDeploy, lastError) |
sandbox_delete |
app_name |
コンテナ停止&削除、作業ツリー & bare repo 削除、cron 解除 |
sandbox_schedule |
app_name, schedule, path, timezone |
cron 式で稼働中アプリへの定期 GET を登録 |
sandbox_unschedule |
app_name |
スケジュールを全解除 |
nickname はツールスキーマには露出しません。SANDBOX_NICKNAME 環境変数で固定 (既定: OS ユーザー名)。
- macOS (本実装の検証環境。Linux でも動くはず)
- Node.js 20+ (Node 24 で確認)
- pnpm
- Docker daemon (Docker Desktop / Colima / Rancher Desktop / Lima のいずれか)
- 自動検出するソケットパス:
$DOCKER_HOST/var/run/docker.sock~/.colima/default/docker.sock~/.rd/docker.sock~/.docker/run/docker.sock~/.lima/default/sock/docker.sock
- 自動検出するソケットパス:
cd /Users/kotaichikawa/works/sandbox-mcp
pnpm install
pnpm buildclaude mcp add sandbox \
node /Users/kotaichikawa/works/sandbox-mcp/dist/index.js \
--env SANDBOX_NICKNAME=kota \
--scope userclaude mcp add sandbox-dev \
npx tsx /Users/kotaichikawa/works/sandbox-mcp/src/index.ts \
--env SANDBOX_NICKNAME=kota \
--scope user{
"mcpServers": {
"sandbox": {
"command": "node",
"args": ["/Users/kotaichikawa/works/sandbox-mcp/dist/index.js"],
"env": { "SANDBOX_NICKNAME": "kota" }
}
}
}登録後、Claude Code を起動して /mcp で sandbox が緑になっているか確認します。
| 変数 | 既定 | 用途 |
|---|---|---|
SANDBOX_NICKNAME |
OS ユーザー名 | アプリ名前空間 (~/.sandbox/apps/{nickname}/...) |
SANDBOX_HOME |
~/.sandbox |
全データの保存場所 |
DOCKER_HOST |
(自動検出) | unix:///path/to/docker.sock または tcp://host:port |
sandbox に "hello" というアプリを作って、index.js で
"hello from sandbox" を返す Express サーバーを書いて publish して
LLM が以下を順に呼びます:
sandbox_init_repo({app_name:"hello"})sandbox_write_file({app_name:"hello", path:"package.json", content:"..."})sandbox_write_file({app_name:"hello", path:"index.js", content:"..."})sandbox_publish({app_name:"hello", description:"..."})sandbox_deploy_status({app_name:"hello"})(status=running になるまで)
curl http://localhost:<port><port> は sandbox_deploy_status または sandbox_list で確認できます。
Claude Code
↓ tool call (stdio JSON-RPC)
sandbox-mcp (Node.js)
├─ ファイル操作 ─→ ~/.sandbox/apps/{nick}/{app}/
├─ git ─→ ~/.sandbox/git/{nick}/{app}.git (bare)
├─ Docker daemon ─→ build → run (port 3001+ 動的割り当て)
└─ node-cron ─→ http://localhost:{port}{path} へ GET
sandbox に「{app_name}」を作って、{機能の説明} を {Node.js / Python / 静的HTML} で実装、
publish してアクセスできる URL を教えて
sandbox の {app_name} の {ファイル名} を {変更内容} に書き換えて、再 publish して
sandbox の {app_name} を毎朝 9 時に /cron/daily を叩くようにスケジュールして (Asia/Tokyo)
sandbox の {app_name} を完全に削除して
sandbox にあるアプリ一覧を見せて
work tree に Dockerfile が無い場合、ファイル構成から自動判定します。
| 検出 | スタック | 生成内容の概要 |
|---|---|---|
package.json |
Node.js | node:20-slim + npm ci、main スクリプトを実行 |
requirements.txt または *.py |
Python | python:3.12-slim + pip install -r requirements.txt + gunicorn (Flask の WSGI app 想定)。requirements.txt が無ければ flask gunicorn を補完 |
index.html のみ |
静的 | nginx:alpine |
| 上記いずれでもない | (Python にフォールバック) | – |
アプリ側の規約: コンテナ内で process.env.PORT (既定 8080) を listen すること。ホスト側のポートは 3001-3999 から動的に割り当てられます。
カスタム Dockerfile を ~/.sandbox/apps/{nick}/{app}/Dockerfile に置けば、それが優先されます。
Docker Desktop / Colima などが起動しているか確認:
docker info | head -3Colima なら colima start。
net.createServer().listen(0) 相当で空きポートを探しますが、3001-3999 が全部埋まっていると失敗します。docker ps で不要なコンテナを停止してください。
~/.sandbox/state.json の同時書込み競合。通常は自動リトライしますが、解消しない場合:
rm -f ~/.sandbox/state.json.lockrm ~/.sandbox/state.jsonで初期化されます (アプリは残るので必要なら sandbox_delete で再作成)。
cron は MCP サーバープロセス (Claude Code 接続中) のみ 動きます。Claude Code を閉じると停止します。 24/7 動作が必要なら本実装の対象外 (launchd plist 化が必要)。
docker logs -f sandbox-mcp__{nickname}__{app_name}~/.sandbox/
├── state.json # アプリ・スケジュール一覧
├── apps/{nickname}/{app_name}/ # 作業ツリー (Dockerfile, ソース等)
├── git/{nickname}/{app_name}.git/ # bare git repo
└── logs/ # コンテナログのスナップショット
- 認証なし: localhost にバインドするだけ。記事の OAuth / Cloudflare ZeroTrust 部分は未実装
- cron は MCP プロセス生存中のみ動作: 記事の Cloud Scheduler とは異なる
- シングルレプリカ: コンテナは 1 アプリ 1 個
- HTTPS なし: ローカル
http://localhost:portのみ - マルチユーザー非対応:
SANDBOX_NICKNAMEで名前空間は分けるが認可なし - 記事の SandboxDB SDK は未実装: 必要なら別途実装 (
~/.sandbox/db/{app}.jsonベースを推奨)
claude mcp remove sandbox
rm -rf /Users/kotaichikawa/works/sandbox-mcp
docker ps -a --format '{{.Names}}' | grep '^sandbox-mcp__' | xargs -r docker rm -f
docker images --format '{{.Repository}}:{{.Tag}}' | grep '^sandbox-mcp/' | xargs -r docker rmi
rm -rf ~/.sandbox# tsx で直接実行 (再ビルド不要)
pnpm dev
# MCP Inspector で対話的にツール叩く
pnpm dev:inspector
# 型チェックのみ
pnpm typecheckログは全て stderr に出ます (stdio transport が stdout を JSON-RPC で専有するため)。