Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions .github/workflows/runloop-blueprint-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@
"system_setup_commands": [
"npm i -g @continuedev/cli@latest",
"sudo apt update",
"sudo apt install -y ripgrep"
]
"sudo apt install -y --no-install-recommends ripgrep chromium chromium-driver xvfb",
"sudo mkdir -p /opt/google/chrome",
"sudo ln -s /usr/bin/chromium /opt/google/chrome/chrome"
],
"launch_parameters": {
"launch_commands": [
"nohup Xvfb :99 -screen 0 1920x1080x24 > /tmp/xvfb.log 2>&1 &",
"sleep 2"
],
"environment_variables": {
"DISPLAY": ":99"
}
}
}
43 changes: 43 additions & 0 deletions Dockerfile.devbox
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Chrome DevTools MCP Development Container
# Based on linux-setup.md instructions

FROM ubuntu:22.04

# Avoid interactive prompts during package installation
ENV DEBIAN_FRONTEND=noninteractive

# Set up display for headless operation
ENV DISPLAY=:99

# Install system dependencies
RUN apt-get update && apt-get install -y \
chromium-browser \
chromium-chromedriver \
xvfb \
curl \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*

# Create symlink for Chrome DevTools MCP
# The MCP looks for Chrome at /opt/google/chrome/chrome
RUN mkdir -p /opt/google/chrome && \
ln -s /usr/bin/chromium-browser /opt/google/chrome/chrome

# Create a startup script to run Xvfb
RUN echo '#!/bin/bash\n\
# Start Xvfb in the background\n\
Xvfb :99 -screen 0 1920x1080x24 &\n\
# Wait a moment for Xvfb to start\n\
sleep 2\n\
# Execute the command passed to docker run\n\
exec "$@"' > /entrypoint.sh && \
chmod +x /entrypoint.sh
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The entrypoint script creation using echo with \n escape sequences is fragile and shell-dependent. Consider using a heredoc for better readability and portability:

RUN cat > /entrypoint.sh <<'EOF'
#!/bin/bash
# Start Xvfb in the background
Xvfb :99 -screen 0 1920x1080x24 &
# Wait a moment for Xvfb to start
sleep 2
# Execute the command passed to docker run
exec "$@"
EOF
chmod +x /entrypoint.sh
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Dockerfile.devbox, line 27:

<comment>The entrypoint script creation using `echo` with `\n` escape sequences is fragile and shell-dependent. Consider using a heredoc for better readability and portability:
```dockerfile
RUN cat &gt; /entrypoint.sh &lt;&lt;&#39;EOF&#39;
#!/bin/bash
# Start Xvfb in the background
Xvfb :99 -screen 0 1920x1080x24 &amp;
# Wait a moment for Xvfb to start
sleep 2
# Execute the command passed to docker run
exec &quot;$@&quot;
EOF
chmod +x /entrypoint.sh
```</comment>

<file context>
@@ -0,0 +1,43 @@
+    ln -s /usr/bin/chromium-browser /opt/google/chrome/chrome
+
+# Create a startup script to run Xvfb
+RUN echo &#39;#!/bin/bash\n\
+# Start Xvfb in the background\n\
+Xvfb :99 -screen 0 1920x1080x24 &amp;\n\
</file context>
Suggested change
RUN echo '#!/bin/bash\n\
# Start Xvfb in the background\n\
Xvfb :99 -screen 0 1920x1080x24 &\n\
# Wait a moment for Xvfb to start\n\
sleep 2\n\
# Execute the command passed to docker run\n\
exec "$@"' > /entrypoint.sh && \
chmod +x /entrypoint.sh
RUN cat > /entrypoint.sh <<'EOF'
#!/bin/bash
# Start Xvfb in the background
Xvfb :99 -screen 0 1920x1080x24 &
# Wait a moment for Xvfb to start
sleep 2
# Execute the command passed to docker run
exec "$@"
EOF
chmod +x /entrypoint.sh

✅ Addressed in a693347


# Set working directory
WORKDIR /workspace

# Use the entrypoint script to ensure Xvfb is running
ENTRYPOINT ["/entrypoint.sh"]

# Default command (can be overridden)
CMD ["/bin/bash"]
229 changes: 229 additions & 0 deletions RUNLOOP_BLUEPRINT_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
# Chrome DevTools MCP Runloop Blueprint

This directory contains scripts to create a Runloop Blueprint for Chrome DevTools MCP development in headless Linux environments.

## What This Blueprint Provides

The blueprint creates a pre-configured development environment with:

### Base Tools (from existing blueprint)

- **Continue CLI**: `@continuedev/cli` installed globally
- **ripgrep**: Fast search tool

### Chrome DevTools MCP Setup

- **Chromium Browser**: For web automation and testing
- **ChromeDriver**: For Selenium/WebDriver support
- **Xvfb**: Virtual display server for headless operation
- **Symlink Configuration**: Chrome DevTools MCP expects Chrome at `/opt/google/chrome/chrome`, symlinked to Chromium
- **Environment Setup**: `DISPLAY=:99` pre-configured
- **Auto-start Xvfb**: Virtual display automatically starts when devbox launches

## Files

- `create_chrome_devtools_blueprint.py` - Python script to create the blueprint
- `create_chrome_devtools_blueprint.ts` - TypeScript script to create the blueprint
- `Dockerfile.devbox` - Original Docker configuration (for reference)
- `linux-setup.md` - Manual setup guide (for reference)

## Prerequisites

- Runloop API access and credentials
- Python 3.7+ (for Python script) or Node.js (for TypeScript script)
- Runloop SDK installed:
- Python: `pip install runloop-api-client`
- TypeScript: `npm install @runloop/api-client`

## Usage

### Python

```bash
# Make executable
chmod +x create_chrome_devtools_blueprint.py

# Run the script
python create_chrome_devtools_blueprint.py
```

### TypeScript

```bash
# Make executable
chmod +x create_chrome_devtools_blueprint.ts

# Run the script
npx ts-node create_chrome_devtools_blueprint.ts
# or if using plain JavaScript
node create_chrome_devtools_blueprint.js
```

### Creating a Devbox from the Blueprint

Once the blueprint is created, you can launch devboxes with:

**Python:**

```python
from runloop_api_client import Runloop

runloop = Runloop()

# By blueprint name (gets latest version)
devbox = await runloop.devboxes.create(
blueprint_name="chrome-devtools-mcp"
)

# By blueprint ID (for specific version)
devbox = await runloop.devboxes.create(
blueprint_id="bpt_xxxxx"
)
```

**TypeScript:**

```typescript
import { Runloop } from "@runloop/api-client";

const runloop = new Runloop();

// By blueprint name (gets latest version)
const devbox = await runloop.devboxes.create({
blueprint_name: "chrome-devtools-mcp",
});

// By blueprint ID (for specific version)
const devbox = await runloop.devboxes.create({
blueprint_id: "bpt_xxxxx",
});
```

## Using Chrome DevTools MCP in the Devbox

Once your devbox is running, Xvfb will already be started. You can use Chrome DevTools MCP tools immediately:

### Open a webpage

```bash
# Use MCP tool or direct chromium command
chromium --headless --disable-gpu --screenshot=screenshot.png https://example.com
```

### Take a screenshot

```bash
chromium --headless --disable-gpu --screenshot=screenshot.png --window-size=1440,900 https://example.com
```

### Upload screenshots

```bash
# Upload to tmpfiles.org for sharing
curl -F "[email protected]" https://tmpfiles.org/api/v1/upload
```

## Blueprint Architecture

The blueprint is based on `runloop:runloop/starter-arm64` and includes:

1. **System Packages**: Installs via `apt-get`

- chromium
- chromium-driver
- xvfb
- curl
- ca-certificates
- ripgrep

2. **NPM Global Packages**:

- @continuedev/cli@latest

3. **Configuration**:

- Creates `/opt/google/chrome/chrome` symlink
- Sets `DISPLAY=:99` environment variable
- Creates startup script at `/home/user/start-xvfb.sh`

4. **Launch Commands**:
- Starts Xvfb in background on devbox creation
- Waits for Xvfb to be ready

## Automated Blueprint Updates

The blueprint is automatically updated on every stable CLI release via the GitHub Actions workflow (`.github/workflows/stable-release.yml`). The blueprint configuration is stored in `.github/workflows/runloop-blueprint-template.json`.

When a new stable version of `@continuedev/cli` is published, the workflow:

1. Publishes the new version to npm
2. Creates a GitHub release
3. Publishes an updated "cn" blueprint to Runloop with the latest configuration

## Troubleshooting

### Blueprint Build Failed

Check the build logs:

```python
blueprint = await runloop.blueprints.from_id("bpt_xxxxx")
log_result = await blueprint.logs()
for log in log_result.logs:
print(f"{log.level}: {log.message}")
```

### Xvfb Not Running in Devbox

Manually start Xvfb:

```bash
Xvfb :99 -screen 0 1920x1080x24 &
```

Or use the startup script:

```bash
/home/user/start-xvfb.sh
```

### Chrome/Chromium Not Found

Verify the symlink:

```bash
ls -la /opt/google/chrome/chrome
which chromium
```

## Blueprint Updates

To update the blueprint:

1. Modify the Dockerfile string in the script
2. Run the script again (it will create a new version)
3. Optionally delete old versions to save storage costs

Example cleanup:

```python
# Get all blueprints with this name
blueprints = await runloop.blueprints.list(name='chrome-devtools-mcp')

# Keep only the newest, delete the rest
sorted_blueprints = sorted(blueprints, key=lambda x: x.created_at, reverse=True)
for old_blueprint in sorted_blueprints[1:]:
await old_blueprint.delete()
```

## Cost Optimization

- Blueprints persist indefinitely and incur storage costs
- Delete unused blueprint versions regularly
- Use blueprint names instead of IDs to always get the latest version

## Related Documentation

- [Runloop Blueprints Documentation](https://docs.runloop.ai/devboxes/blueprints)
- [Chrome DevTools MCP Setup Guide](./linux-setup.md)
- [Runloop SDK Reference](https://docs.runloop.ai/)
97 changes: 97 additions & 0 deletions create_chrome_devtools_blueprint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/usr/bin/env python3
"""
Create a Runloop Blueprint for Chrome DevTools MCP Development
Based on linux-setup.md instructions
"""

import time
from runloop_api_client import Runloop

def create_chrome_devtools_blueprint():
"""Create a blueprint with Chrome DevTools MCP setup"""

# Initialize Runloop client
runloop = Runloop()

# Dockerfile based on Runloop base image with Chrome DevTools MCP setup
dockerfile = """FROM runloop:runloop/starter-arm64

# Set up display for headless operation
ENV DISPLAY=:99

# Install system dependencies (including base blueprint tools)
RUN apt-get update && apt-get install -y --no-install-recommends \\
chromium \\
chromium-driver \\
xvfb \\
curl \\
ca-certificates \\
ripgrep \\
&& rm -rf /var/lib/apt/lists/*

# Install Continue CLI
RUN npm i -g @continuedev/cli@latest

# Create symlink for Chrome DevTools MCP
# The MCP looks for Chrome at /opt/google/chrome/chrome
RUN sudo mkdir -p /opt/google/chrome && \\
sudo ln -s /usr/bin/chromium /opt/google/chrome/chrome

# Create a startup script to run Xvfb
RUN echo '#!/bin/bash\\n\\
# Start Xvfb in the background\\n\\
Xvfb :99 -screen 0 1920x1080x24 &\\n\\
# Wait a moment for Xvfb to start\\n\\
sleep 2' > /home/user/start-xvfb.sh && \\
chmod +x /home/user/start-xvfb.sh

WORKDIR /home/user
"""

# Create the blueprint
print("Creating Chrome DevTools MCP blueprint...")
blueprint = runloop.blueprints.create(
name="chrome-devtools-mcp",
dockerfile=dockerfile,
launch_parameters={
"launch_commands": [
# Start Xvfb on devbox launch
"nohup Xvfb :99 -screen 0 1920x1080x24 > /tmp/xvfb.log 2>&1 &",
# Wait for Xvfb to be ready
"sleep 2"
]
}
)

print(f"Blueprint created with ID: {blueprint.id}")

# Wait for blueprint to build
print("Waiting for blueprint to build...")

while blueprint.status not in ["ready", "failed"]:
time.sleep(5)
# Refresh blueprint info
blueprints = runloop.blueprints.list()
for bp in blueprints:
if bp.id == blueprint.id:
blueprint = bp
break
print(f"Blueprint status: {blueprint.status}")

if blueprint.status == "failed":
print("Blueprint build failed. Checking logs...")
log_result = runloop.blueprints.logs(blueprint.id)
for log in log_result.logs:
print(f"{log.level}: {log.message}")
return None

print(f"Blueprint build complete! ID: {blueprint.id}")
print("\nTo create a devbox from this blueprint:")
print(f'devbox = blueprint.create_devbox()')
print(f'# or')
print(f'devbox = runloop.devboxes.create(blueprint_id="{blueprint.id}")')

return blueprint

if __name__ == "__main__":
create_chrome_devtools_blueprint()
Loading
Loading