Build your first tool
Two ways in. If you use an AI assistant, take the first one — there's nothing to install.
Option A — connect Pebble to your AI recommended
1. Add Pebble as a connector. In Claude Code (terminal):
claude mcp add --transport http pebble https://shippebble.com/mcp
On claude.ai: Settings → Connectors → Add
custom connector → paste https://shippebble.com/mcp. Other
MCP-capable assistants (Cursor, …): add the same URL as a remote MCP
server.
2. Approve the sign-in. Your browser opens a Pebble page — sign in with Google, click Approve. That's the whole setup.
3. Check it worked. Ask your assistant:
what pebble tools do you have?
It should list six: get_contract, ship_tool, share_tool,
list_tools, get_logs, restore_version. (In Claude Code you can also
type /mcp and select pebble to see them.)
4. Ask for what you want. For example:
Build us a lunch-order tool — people pick from a menu, kitchen sees the list at 11:30 — and ship it for the team.
The assistant fetches Pebble's build rules itself, writes the tool, ships it, and hands you the link. It can also share with specific people, read logs when something's off, and roll back to an earlier version — just ask.
Option B — the classic way (terminal)
1. Install the CLI (one line, no sudo —
copies a single Python script to ~/.local/bin):
curl -fsSL https://shippebble.com/install | sh
2. Connect it to your account:
pebble login
3. Build the tool — let your AI assistant do it. Paste this into your assistant together with a description of what you want:
You are building a small internal web tool that will be deployed on Pebble (a microVM platform). Follow this contract exactly:
- One folder = one tool. Everything lives in it.
- `run.sh` (required, executable): starts an HTTP server listening on
0.0.0.0:$PORT. Example: `#!/bin/sh` + `exec python3 /app/app.py`.
The folder is mounted at /app inside the VM.
- Language: Python 3 or Node.js. Dependencies are installed at ship time
inside an isolated build sandbox (never on the host):
- Python: list packages in `requirements.txt` → installed into
`/app/venv` (start your app with `/app/venv/bin/python`).
- Node: add a `package.json` → `npm install` runs and `node_modules`
lands in /app (start with `node`, e.g. `exec node /app/server.js`).
Stdlib/no-dependency tools ship fastest (no build step).
- Persistent state: write ONLY under /data (e.g. SQLite at
/data/app.db). Everything else is wiped on every restart.
- Network: outbound is BLOCKED by default. To call an external API,
prefer a named connection: an org member registers it once (e.g.
"hubspot" with its API token), the tool declares it in `pebble.toml`:
connections = ["hubspot"]
and the code calls `http://hubspot.pebble.internal/...` with NO
credential — the platform attaches the real token outside the VM.
Never put API keys in code, env files, or secrets for services that
have a connection. List existing connections: GET /api/connections;
services you can add by name: GET /api/catalog.
Best practice — declare the LEAST access the tool needs, so a bug can't
do more than intended (the platform enforces it and the owner sees it):
connections = ["hubspot"]
[connection.hubspot]
methods = ["GET"] # omit = any method
paths = ["/crm/v3/objects/contacts*"] # omit = any path; * = prefix
Requests outside the declared methods/paths are refused by the platform.
If a connection is per-user (each viewer uses their own account), forward
the `X-Pebble-Actor` header you received on the incoming request to the
connection so the call runs as the current viewer:
actor = incoming_request.headers["X-Pebble-Actor"]
# then send it on your outbound call to .pebble.internal
IMPORTANT: if a connection you need does not exist yet, do NOT ask the
human to paste the API key to you — a key typed into chat defeats the
whole point. Send them to https://shippebble.com/connections to add it once (pick the
service, paste the key there), then ship again.
For raw non-credentialed egress (rare), the legacy form still works:
[egress]
allow = ["api.example.com:443"]
- Optional `pebble.toml` extras:
[tool]
mem = 256 # MB, default 256
[cron.jobname]
path = "/cron/tick" # GET endpoint to call
every = 3600 # seconds, minimum 900 (shorter would keep
# the VM awake around the clock — rejected)
- Auth is already handled by the platform. Every request carries the
signed-in user's email in the `X-Pebble-User` header — trust it.
- Keep the UI a single server-rendered HTML page unless asked otherwise.
When the tool is ready, ship it:
- If you have Pebble MCP tools available (ship_tool), call ship_tool
yourself with the folder's files and tell the human the live URL.
- Otherwise the human ships it from the tool folder with:
pebble ship --name
Either way, the ship response returns the tool's live URL (each tool gets
its own isolated address) — share that with the team.
4. Ship it from the tool folder:
pebble ship --name my-tool
Either way, seconds later it's live and shareable from your team drawer.