Install + configure EthPandaOps Panda
Panda is a CLI + MCP server +
sandboxed Python runtime that gives a single authenticated entry point
to the EthPandaOps data ecosystem (Xatu ClickHouse, Prometheus, Loki,
Eth nodes). Install it once per server; both panda CLI and Claude's
MCP integration use the same local server.
When to use this skill
- The user wants to query Xatu data, Prometheus metrics, Loki logs, or Ethereum-node data from their environment.
- They've asked to enable Claude's
ethpandaops-pandaMCP integration. - They need this on a new server (it's per-host: each server runs its own local Panda server pointing at the shared hosted proxy).
Step 1 — Choose install mode (REQUIRED PROMPT)
Before any install action, ask the user via AskUserQuestion which
mode they want — the answer drives every later step:
| option | when to choose |
|---|---|
| Hosted proxy | User has an EthPandaOps-allowlisted GitHub identity. Easiest path. |
| Self-hosted proxy | User's org has its own ClickHouse / Prometheus / Loki credentials and prefers not to depend on the EthPandaOps proxy. |
| Skip the proxy step | Just install the CLI for now; configure later. |
Question to ask:
header: "Panda mode"
question: "Which Panda install mode do you want?"
options:
- Hosted proxy (recommended, requires EthPandaOps GitHub allowlist)
- Self-hosted proxy (org has own ClickHouse/Prometheus/Loki credentials)
- Skip proxy step (install CLI only)
If "Hosted proxy" → continue to step 2.
If "Self-hosted proxy" → ask the credential prompts in step 4.
If "Skip" → install the CLI only, skip panda init.
Pre-flight checks
docker --version # must be 20+ with Compose plugin
docker compose version # must succeed (Compose v2)
which curl # required for the install script
If Docker is missing, abort and tell the user to install Docker first — Panda's runtime is Docker-based.
Install
curl -sSfL https://raw.githubusercontent.com/ethpandaops/panda/master/scripts/install.sh | sh
This installs the panda CLI to ~/.local/bin/. Make sure ~/.local/bin
is on PATH:
echo $PATH | grep -q "$HOME/.local/bin" || echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
Step 3 — Initialize (hosted proxy mode)
Run interactively in foreground (NOT via run_in_background — the
OAuth flow needs the user to copy a URL):
panda init
This:
- Pulls the panda Docker images (~500 MB).
- Writes a default config at
~/.config/panda/config.yaml. - Prints a GitHub OAuth URL — opens browser if a display is available, else prints a device-flow URL the user opens from any browser.
- Starts the local panda server on port 2480.
REQUIRED OAUTH HANDOFF — two-step format: the device-flow URL and code MUST be presented as plain markdown FIRST so they render as a clickable link / copyable code in the user's UI. Then ask a minimal question with NO embedded URL/code (AskUserQuestion can't reliably render long URLs or distinct copyable strings).
Step 1 — output the URL + code as a markdown text message (regular output, not a tool call):
**URL:** <paste the dex URL from panda auth output>
**Code:** `<paste the code from panda auth output>`
Steps in browser:
1. Click the URL above
2. Sign in with the EthPandaOps-allowlisted GitHub identity
3. Paste the code when prompted
4. Approve the access
The `panda auth login` process is sitting waiting — it will complete
on its own when the browser side is done.
Step 2 — minimal AskUserQuestion for the next step:
header: "Status"
question: "OAuth done?"
options:
- Done — verify now
- Need a fresh code
- Hit an error (tell me what the browser said)
Why two steps: AskUserQuestion option labels are short (≤5 words each) and the question text doesn't render long URLs as clickable in most clients. Putting the URL/code in a plain message first lets the user click + copy with their normal terminal/UI affordances.
If OAuth fails (user not allowlisted, network issue, expired code), fall back via:
header: "Allowlist?"
question: "OAuth failed — GitHub identity may not be allowlisted. What next?"
options:
- Request allowlist (re-run skill when granted)
- Switch to self-hosted proxy
- Install CLI only
Recognising the allowlist failure mode: the symptom is GitHub
returning bad_verification_code during the code-exchange step.
That's NOT an expired code — it's GitHub refusing to issue a token
because the user can't access the upstream OAuth app
(panda-proxy). Distinguish from a genuinely expired code by
checking that the failure is immediate after device-flow approval,
not after a multi-minute wait.
When the user picks "Request allowlist", give them the request template (don't make them assemble it themselves):
| field | value |
|---|---|
| GitHub identity | |
| Service | panda-proxy (panda-proxy.ethpandaops.io) |
| Dex issuer | https://dex.primary.production.platform.ethpandaops.io |
| OAuth client ID | panda-proxy |
| Reason | <user's project + team> |
| Reproducer | panda auth login --no-browser returns bad_verification_code after entering the device code |
Where to send: EthPandaOps Slack/Discord, or GitHub issue on
ethpandaops/panda. Visible maintainers in the proxy config's
success-page rules: samcm, mattevans.
Also stop the local panda server while waiting — it will flap because the proxy embedding backend is unreachable without auth:
panda server stop
Step 4 — Self-host the proxy (alternative path)
For orgs with their own ClickHouse / Prometheus / Loki credentials.
REQUIRED PROMPT — gather credentials structurally via
AskUserQuestion. Multiple questions in one batch:
question: "Which datasources do you want to configure on the
self-hosted proxy?"
multiSelect: true
header: "Datasources"
options:
- ClickHouse (e.g. Xatu)
- Prometheus
- Loki
- Ethereum node (RPC)
question: "Provide the ClickHouse host:port (leave empty if N/A)"
header: "ClickHouse host"
options:
- localhost:8443
- clickhouse.internal:8443
(user will likely select Other and supply their host)
question: "ClickHouse credentials — username only here; ask for the
password separately to keep it off the question prompt"
header: "ClickHouse user"
options:
- default
- readonly
For SECRETS (passwords, tokens), do NOT use AskUserQuestion (it logs
to telemetry). Instead, prompt the user to set environment variables
and reference them in the config:
echo 'export CH_PASSWORD="..."' >> ~/.bashrc # user does this
echo 'export PROM_PASSWORD="..."' >> ~/.bashrc # if using Prometheus
echo 'export LOKI_PASSWORD="..."' >> ~/.bashrc # if using Loki
Then write the proxy config that references the env vars:
mkdir -p ~/panda-proxy && cat > ~/panda-proxy/proxy-config.yaml <<EOF
server:
listen_addr: ":18081"
auth:
mode: none
clickhouse:
- name: my-cluster
host: "${CH_HOST:-localhost}"
port: ${CH_PORT:-8443}
database: ${CH_DB:-default}
username: "${CH_USER:-default}"
password: "${CH_PASSWORD}"
secure: true
EOF
docker run -d --name panda-proxy -p 18081:18081 \
-e CH_PASSWORD -e PROM_PASSWORD -e LOKI_PASSWORD \
-v ~/panda-proxy/proxy-config.yaml:/config/proxy-config.yaml:ro \
--entrypoint /app/panda-proxy \
ethpandaops/panda:server-latest \
--config /config/proxy-config.yaml
Point the panda CLI at it via ~/.config/panda/config.yaml:
proxy:
url: "http://localhost:18081"
Then panda init skips the OAuth flow.
See proxy-config.example.yaml for the full schema (Prometheus, Loki, ethnodes too).
Verify
panda server status # should report "running"
panda datasources # lists available datasources via the proxy
panda execute --code 'print("hello from sandbox")'
If panda datasources returns nothing, the proxy auth or proxy URL is
wrong. Common issues:
- GitHub identity not on the allowlist → ask the user to coordinate with EthPandaOps.
- Stale token →
panda auth refresh(checkpanda --helpfor the exact subcommand on the installed version). - Docker container exited →
docker logs panda-serverfor the trace.
Wire into Claude Code (MCP)
Edit ~/.claude.json to add the MCP server entry:
{
"mcpServers": {
"ethpandaops-panda": {
"type": "http",
"url": "http://localhost:2480/mcp"
}
}
}
Restart Claude Code (or whatever MCP client). The tools execute_python,
manage_session, and search should appear under the ethpandaops-panda
namespace.
What Panda exposes (datasources)
| datasource | what's there |
|---|---|
ClickHouse xatu |
Beacon-chain data lake (blocks, attestations, validators, etc.) |
Prometheus primary |
Time-series metrics from EthPandaOps infrastructure |
Loki primary |
Logs from EthPandaOps infrastructure |
ethnode |
Direct Ethereum node access (read-only RPC) |
Benchmarkoor results are not (as of 2026-05-10) a directly named
datasource. The benchmarkoor service stores output in S3
(repricings/results) gated by the same GitHub OAuth. Check via
Panda whether they've been ingested into Prometheus or ClickHouse:
# Inside `panda execute --code '...'`:
import panda
# Try Prometheus
print(panda.prometheus("primary").query("benchmarkoor_test_duration_seconds"))
# Try ClickHouse table listing
print(panda.clickhouse("xatu").execute("SHOW TABLES LIKE '%benchmark%'"))
If neither returns results, file an EthPandaOps request to ingest benchmarkoor results into one of Panda's datasources. Without that, benchmarkoor data still requires the gated HTTP API.
Troubleshooting
panda inithangs at OAuth: the browser callback URL may not be reachable from the auth issuer. Use the device-flow alternative:panda auth login --device.- Docker pull fails behind proxy: configure Docker daemon proxy
settings before running
panda init. - Multiple servers, single user: the OAuth flow is per-server.
Each server needs its own
panda initagainst the same GitHub identity. Consider a self-hosted proxy if managing many servers becomes painful. - MCP tools don't appear in Claude: confirm
panda server statusreports running; confirm port 2480 is reachable from where Claude runs (same machine OK; different machine = need to expose port).
Cross-check before considering install successful
panda server status # running
panda datasources # at least 1 datasource listed
panda execute --code 'print("ok")' # prints "ok"
If all three pass, the skill's job is done. Hand the user the list of available datasources so they know what to query next.