Skip to content

Commit bae2ba4

Browse files
committed
cp dines
1 parent a361aa9 commit bae2ba4

7 files changed

Lines changed: 207 additions & 8 deletions

File tree

EXAMPLES.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Examples
2+
3+
Runnable examples live in the [`examples/`](./examples) directory. Each script is self-contained:
4+
5+
```sh
6+
python examples/<example>.py
7+
```
8+
9+
## Available Examples
10+
11+
### [MCP Hub + Claude Code + GitHub](./examples/mcp_github_claude_code.py)
12+
13+
Launches a devbox with GitHub's MCP server attached via **MCP Hub**, installs **Claude Code**, and asks Claude to list repositories — all without the devbox seeing your real GitHub credentials.
14+
15+
**What it does:**
16+
17+
1. Creates an MCP config pointing at `https://api.githubcopilot.com/mcp/`
18+
2. Stores a GitHub PAT as a Runloop secret (credential isolation)
19+
3. Launches a devbox with MCP Hub enabled — the devbox receives `$RL_MCP_URL` and `$RL_MCP_TOKEN`
20+
4. Installs Claude Code (`@anthropic-ai/claude-code`)
21+
5. Registers the MCP Hub endpoint with Claude Code via `claude mcp add`
22+
6. Runs `claude --print` to ask Claude to list repos using the GitHub MCP tools
23+
7. Cleans up all resources
24+
25+
```sh
26+
GITHUB_TOKEN=ghp_xxx ANTHROPIC_API_KEY=sk-ant-xxx python examples/mcp_github_claude_code.py
27+
```
28+
29+
---
30+
31+
**See also:** [MCP Hub documentation](https://docs.runloop.ai/docs/devboxes/mcp-hub) · [Runloop docs](https://docs.runloop.ai)

examples/.keep

Lines changed: 0 additions & 4 deletions
This file was deleted.

examples/mcp_github_claude_code.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#!/usr/bin/env python3
2+
"""MCP Hub + Claude Code + GitHub
3+
4+
Launches a devbox with GitHub's MCP server attached via MCP Hub,
5+
installs Claude Code, registers the MCP endpoint, and asks Claude
6+
to list repositories — all without the devbox seeing your real
7+
GitHub credentials.
8+
9+
Prerequisites:
10+
RUNLOOP_API_KEY — your Runloop API key
11+
GITHUB_TOKEN — a GitHub PAT with repo scope
12+
ANTHROPIC_API_KEY — your Anthropic API key (for Claude Code)
13+
14+
Usage:
15+
GITHUB_TOKEN=ghp_xxx ANTHROPIC_API_KEY=sk-ant-xxx python examples/mcp_github_claude_code.py
16+
"""
17+
18+
from __future__ import annotations
19+
20+
import os
21+
import sys
22+
import time
23+
24+
from runloop_api_client import RunloopSDK
25+
26+
GITHUB_MCP_ENDPOINT = "https://api.githubcopilot.com/mcp/"
27+
28+
29+
def main() -> None:
30+
github_token = os.environ.get("GITHUB_TOKEN")
31+
anthropic_key = os.environ.get("ANTHROPIC_API_KEY")
32+
33+
if not github_token:
34+
print("Set GITHUB_TOKEN to a GitHub PAT with repo scope.", file=sys.stderr)
35+
sys.exit(1)
36+
if not anthropic_key:
37+
print("Set ANTHROPIC_API_KEY for Claude Code.", file=sys.stderr)
38+
sys.exit(1)
39+
40+
sdk = RunloopSDK()
41+
secret_name = f"example-github-mcp-{int(time.time())}"
42+
43+
# 1. Create an MCP config for the GitHub MCP server
44+
print("Creating MCP config…")
45+
mcp_config = sdk.mcp_config.create(
46+
name=f"github-example-{int(time.time())}",
47+
endpoint=GITHUB_MCP_ENDPOINT,
48+
allowed_tools=["*"],
49+
description="GitHub MCP server — example",
50+
)
51+
print(f" MCP config: {mcp_config.id}")
52+
53+
# 2. Store the GitHub PAT as a Runloop secret
54+
print("Storing GitHub token as a secret…")
55+
sdk.api.secrets.create(name=secret_name, value=github_token)
56+
57+
devbox = None
58+
try:
59+
# 3. Launch a devbox with MCP Hub enabled
60+
print("Creating devbox with MCP Hub…")
61+
devbox = sdk.devbox.create(
62+
name=f"mcp-claude-code-{int(time.time())}",
63+
launch_parameters={
64+
"resource_size_request": "SMALL",
65+
"keep_alive_time_seconds": 300,
66+
},
67+
mcp=[{"mcp_config": mcp_config.id, "secret": secret_name}],
68+
)
69+
print(f" Devbox ready: {devbox.id}")
70+
71+
# 4. Install Claude Code
72+
print("\nInstalling Claude Code…")
73+
install_result = devbox.cmd.exec("npm install -g @anthropic-ai/claude-code")
74+
if install_result.exit_code != 0:
75+
print("Failed to install Claude Code:", install_result.stderr(), file=sys.stderr)
76+
return
77+
print(" Installed.")
78+
79+
# 5. Register MCP Hub with Claude Code
80+
print("Registering MCP Hub endpoint with Claude Code…")
81+
add_result = devbox.cmd.exec(
82+
'claude mcp add runloop-mcp --transport http "$RL_MCP_URL" '
83+
'--header "Authorization: Bearer $RL_MCP_TOKEN"'
84+
)
85+
if add_result.exit_code != 0:
86+
print("Failed to add MCP server:", add_result.stderr(), file=sys.stderr)
87+
return
88+
print(" MCP server registered.")
89+
90+
# 6. Ask Claude Code to list repos
91+
print("\nAsking Claude Code to list runloopai repos…\n")
92+
claude_result = devbox.cmd.exec(
93+
f"ANTHROPIC_API_KEY={anthropic_key} claude -p "
94+
'"Use the MCP tools to list all repositories in the runloopai GitHub org. '
95+
'Just output the repo names, one per line." '
96+
"--dangerously-skip-permissions"
97+
)
98+
99+
output = claude_result.stdout().strip()
100+
print("── Claude Code output ──────────────────────────────")
101+
print(output)
102+
print("────────────────────────────────────────────────────")
103+
104+
finally:
105+
print("\nCleaning up…")
106+
if devbox:
107+
try:
108+
devbox.shutdown()
109+
except Exception:
110+
pass
111+
try:
112+
mcp_config.delete()
113+
except Exception:
114+
pass
115+
try:
116+
sdk.api.secrets.delete(secret_name)
117+
except Exception:
118+
pass
119+
print("Done.")
120+
121+
122+
if __name__ == "__main__":
123+
main()

src/runloop_api_client/sdk/blueprint.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,21 @@
1212

1313

1414
class Blueprint:
15-
"""Synchronous wrapper around a blueprint resource."""
15+
"""Synchronous wrapper around a blueprint resource.
16+
17+
Blueprints are reusable devbox templates built from Dockerfiles. They define the
18+
base image, installed packages, and system configuration. Create blueprints via
19+
``runloop.blueprint.create()`` and then launch devboxes from them.
20+
21+
Example:
22+
>>> runloop = RunloopSDK()
23+
>>> blueprint = runloop.blueprint.create(
24+
... name="python-ml",
25+
... dockerfile="FROM ubuntu:22.04\\nRUN apt-get update && apt-get install -y python3",
26+
... )
27+
>>> logs = blueprint.logs()
28+
>>> devbox = blueprint.create_devbox(name="ml-workbench")
29+
"""
1630

1731
def __init__(
1832
self,

src/runloop_api_client/sdk/network_policy.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,20 @@
1010

1111

1212
class NetworkPolicy:
13-
"""Synchronous wrapper around a network policy resource."""
13+
"""Synchronous wrapper around a network policy resource.
14+
15+
Network policies control egress network access for devboxes. They specify
16+
allowed hostnames via glob patterns and whether devbox-to-devbox traffic is
17+
permitted. Apply policies when creating devboxes or blueprints.
18+
19+
Example:
20+
>>> runloop = RunloopSDK()
21+
>>> policy = runloop.network_policy.create(
22+
... name="restricted",
23+
... allowed_hostnames=["github.com", "*.npmjs.org"],
24+
... )
25+
>>> devbox = runloop.devbox.create(name="locked-down", network_policy_id=policy.id)
26+
"""
1427

1528
def __init__(
1629
self,

src/runloop_api_client/sdk/snapshot.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,17 @@
1818

1919

2020
class Snapshot:
21-
"""Wrapper around synchronous snapshot operations."""
21+
"""Synchronous wrapper around a disk snapshot resource.
22+
23+
Snapshots capture the full disk state of a devbox. Create snapshots via
24+
``devbox.snapshot_disk()`` or ``devbox.snapshot_disk_async()``, then restore
25+
them into new devboxes with ``snapshot.create_devbox()``.
26+
27+
Example:
28+
>>> snapshot = devbox.snapshot_disk(name="checkpoint-v1")
29+
>>> new_devbox = snapshot.create_devbox(name="restored")
30+
>>> snapshot.delete()
31+
"""
2232

2333
def __init__(
2434
self,

src/runloop_api_client/sdk/storage_object.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,19 @@
1313

1414

1515
class StorageObject:
16-
"""Wrapper around storage object operations, including uploads and downloads."""
16+
"""Synchronous wrapper around a storage object resource.
17+
18+
Storage objects hold uploaded files and archives (text, binary, tgz). They can be
19+
downloaded, mounted into devboxes, or used as blueprint build contexts. Use the
20+
convenience upload helpers on ``runloop.storage_object`` to create objects from
21+
text, bytes, files, or directories.
22+
23+
Example:
24+
>>> runloop = RunloopSDK()
25+
>>> obj = runloop.storage_object.upload_from_text("Hello!", name="greeting.txt")
26+
>>> print(obj.download_as_text()) # "Hello!"
27+
>>> obj.delete()
28+
"""
1729

1830
def __init__(self, client: Runloop, object_id: str, upload_url: str | None) -> None:
1931
"""Initialize the wrapper.

0 commit comments

Comments
 (0)