# easypanel-mcp-server > MCP Server for full Easypanel control via Claude Code, Cursor and Claude Desktop. [![npm version](https://img.shields.io/npm/v/easypanel-mcp-server.svg?style=flat-square)](https://www.npmjs.com/package/easypanel-mcp-server) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](LICENSE) [![GitHub Stars](https://img.shields.io/github/stars/helbertparanhos/easypanel-mcp-server?style=flat-square)](https://github.com/helbertparanhos/easypanel-mcp-server/stargazers) [![GitHub Forks](https://img.shields.io/github/forks/helbertparanhos/easypanel-mcp-server?style=flat-square)](https://github.com/helbertparanhos/easypanel-mcp-server/network/members) [![GitHub Issues](https://img.shields.io/github/issues/helbertparanhos/easypanel-mcp-server?style=flat-square)](https://github.com/helbertparanhos/easypanel-mcp-server/issues) [![CI](https://github.com/helbertparanhos/easypanel-mcp-server/actions/workflows/ci.yml/badge.svg)](https://github.com/helbertparanhos/easypanel-mcp-server/actions/workflows/ci.yml) [![Glama Quality](https://glama.ai/mcp/servers/helbertparanhos/easypanel-mcp-server/badges/score.svg)](https://glama.ai/mcp/servers/helbertparanhos/easypanel-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org/) [![Node.js](https://img.shields.io/badge/Node.js-339933?style=flat-square&logo=nodedotjs&logoColor=white)](https://nodejs.org/) [![MCP](https://img.shields.io/badge/MCP-Model%20Context%20Protocol-000000?style=flat-square)](https://modelcontextprotocol.io/) [![Claude Code](https://img.shields.io/badge/Claude%20Code-D97706?style=flat-square)](https://claude.ai/code) [![Cursor](https://img.shields.io/badge/Cursor-Compatible-4F46E5?style=flat-square)](https://cursor.sh) [![Claude Desktop](https://img.shields.io/badge/Claude%20Desktop-Compatible-D97706?style=flat-square)](https://claude.ai/download) [![Instagram](https://img.shields.io/badge/@helbertparanhos-E4405F?style=flat-square&logo=instagram&logoColor=white)](https://www.instagram.com/helbertparanhos) [![YouTube](https://img.shields.io/badge/stratacademy-FF0000?style=flat-square&logo=youtube&logoColor=white)](https://www.youtube.com/@stratacademy) [![LinkedIn](https://img.shields.io/badge/helbert--paranhos-0077B5?style=flat-square&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/helbert-paranhos/) [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-FFDD00?style=flat-square&logo=buy-me-a-coffee&logoColor=black)](https://buymeacoffee.com/helbertparanhos) [![Strat Academy](https://img.shields.io/badge/Strat%20Academy-8B5CF6?style=flat-square)](https://stratacademy.com.br) --- ## What is this **easypanel-mcp-server** connects Claude Code, Cursor and Claude Desktop directly to your [Easypanel](https://easypanel.io) instance — a modern Docker-based server control panel — through the Model Context Protocol. Instead of switching between your editor and the Easypanel dashboard, you control everything from inside Claude: deploy from GitHub, update env vars, read live logs, exec into containers, manage domains, databases, volumes and ports, set resource limits, run Docker maintenance and monitor your server — all in natural language. It maps the Easypanel API to **57 typed tools** across 15 categories, plus a single `easypanel_raw` escape hatch that reaches **any** of Easypanel's ~375 API operations for everything not covered by a dedicated tool. It speaks **all three API generations** — the tRPC API of panels ≤ 2.30, the RPC layer of 2.31–2.32, and the **public API introduced in Easypanel 2.33** — auto-detecting which one your panel uses. Every destructive action is gated behind an explicit confirmation, every response opens with a context banner so Claude always knows what it is touching, and an optional read-only mode lets you connect safely to a production panel. 📖 API reference: [`docs/easypanel-api.md`](docs/easypanel-api.md) — architecture, the API generations, confirmed operations mapped tool-by-tool, and how to discover new ones. --- ## Compatibility Easypanel changed its API twice in quick succession: - **2.31** replaced the internal tRPC API with an RPC layer (`/api/rpc/*`). On panels ≥ 2.31, every v1.x call that carries parameters fails with `400 Input validation failed`. - **2.33** shipped a **documented public API** (`/api/`, GET for reads, POST for writes) and stated that the old internal API *"may change without notice and should not be relied upon"*. v3 targets the public API on those panels. | Your Easypanel version | Use | |---|---| | **any** (recommended) | `easypanel-mcp-server@latest` (**v3.x**) — auto-detects the generation, works on all three | | **≤ 2.30.x** only, pinned | `easypanel-mcp-server@legacy` (**v1.3.x**) — frozen tRPC-only line, last validated against v2.30.1 | v3 detects the generation with a single probe request on first call (cached) and logs the panel version to stderr. To skip detection, set `EASYPANEL_API_FLAVOR` to `trpc` (≤ 2.30), `rpc` (2.31–2.32) or `public` (≥ 2.33). > **Upgrading from v2?** If you pinned `EASYPANEL_API_FLAVOR=rpc` to work around the 2.32 issues, **remove it** — otherwise the client stays on the internal API that Easypanel now declares unstable. ### This MCP vs. Easypanel's built-in MCP Easypanel 2.33 also ships its own MCP endpoint (`/api/mcp`, connection details next to your API key). It's a thin wrapper over the public API. This server is a different trade-off: | | Easypanel built-in MCP | easypanel-mcp-server | |---|---|---| | Runtime container logs | — | ✅ via `/ws/serviceLogs` (no Loki/licence needed) | | Exec inside a container | — | ✅ `exec_in_container` with destructive-command gate | | Live Docker events | — | ✅ `get_docker_events` | | Read-only mode | — | ✅ `MCP_ACCESS_MODE=readonly` blocks every write at the source | | Confirmation gate on destructive ops | — | ✅ `confirm: "CONFIRMO"` | | Secret redaction (`list_users`) | — | ✅ strips `apiToken` / `twoFactorSecret` | | Env var read-modify-write | — | ✅ never clobbers other variables | | Connection-string building | — | ✅ `inspect_database` | | Coverage of every API operation | ✅ | ✅ via `easypanel_raw` | | Zero install | ✅ | needs npx/node | Using both at once is fine — they don't conflict. --- ## Prerequisites - Easypanel instance running and accessible - API token — generate at **Easypanel → Settings → API → Generate Token** - Node.js ≥ 18 and Claude Code, Cursor or Claude Desktop --- ## Quick start ### Option A — npx (no install needed) Add `.mcp.json` to your project root: ```json { "mcpServers": { "easypanel-mcp": { "command": "npx", "args": ["-y", "easypanel-mcp-server"], "env": { "EASYPANEL_URL": "https://your-panel.example.com", "EASYPANEL_TOKEN": "your-api-token" } } } } ``` ### Option B — local build ```bash git clone https://github.com/helbertparanhos/easypanel-mcp-server cd easypanel-mcp-server npm install && npm run build ``` ```json { "mcpServers": { "easypanel-mcp": { "command": "node", "args": ["/ABSOLUTE/PATH/easypanel-mcp-server/dist/index.js"], "env": { "EASYPANEL_URL": "https://your-panel.example.com", "EASYPANEL_TOKEN": "your-api-token" } } } } ``` ### Cursor — reuse env vars across projects In **Cursor Settings → Tools & MCPs → Environment Variables**, set: - `EASYPANEL_URL` = `https://your-panel.example.com` - `EASYPANEL_TOKEN` = `your-api-token` Then your `.cursor/mcp.json` uses references that apply automatically to every project: ```json { "mcpServers": { "easypanel-mcp": { "command": "npx", "args": ["-y", "easypanel-mcp-server"], "env": { "EASYPANEL_URL": "${EASYPANEL_URL}", "EASYPANEL_TOKEN": "${EASYPANEL_TOKEN}" } } } } ``` --- ## Environment variables | Variable | Required | Default | Description | |----------|----------|---------|-------------| | `EASYPANEL_URL` | ✅ | — | Panel URL, no trailing slash (e.g. `https://panel.example.com`) | | `EASYPANEL_TOKEN` | ✅ | — | API token (Easypanel → Settings → API → Generate Token) | | `MCP_ACCESS_MODE` | — | `full` | Set to `readonly` to block **all** writes (curated tools **and** `easypanel_raw`). Reads stay available — ideal for connecting to a production panel for inspection only. | | `EASYPANEL_API_FLAVOR` | — | _(auto)_ | Force the panel's API generation instead of auto-detecting: `trpc` (≤ 2.30), `rpc` (2.31–2.32) or `public` (≥ 2.33). Aliases: `legacy` / `modern`. Leave unset unless you have a reason — a stale `rpc` pin keeps a 2.33 panel on the internal API. | | `EASYPANEL_RAW_DISABLED` | — | _(enabled)_ | Set to `1` to fully disable the `easypanel_raw` escape hatch. Recommended when the MCP is exposed to untrusted content (prompt-injection risk), since `easypanel_raw` reads can return secrets and are **not** covered by read-only mode. | --- ## Adding context to a project Place this in your project's `CLAUDE.md` so Claude knows which Easypanel project and service it should operate on by default: ```markdown ## Easypanel Project: `my-project` | Service: `my-api` | Branch: `main` Repo: `owner/repo` ``` No folder copying needed — one MCP install serves all your projects. --- ## Use cases > **"Deploy my app"** — Claude lists projects, inspects the current service, triggers `deploy_service`, then watches `list_actions` until it completes. > **"Why is my service down?"** — Claude calls `get_service_error`, `get_service_logs` and `get_build_logs`, and can `exec_in_container` to inspect files/env live. > **"Add DATABASE_URL to staging"** — Claude reads current env vars with `get_env_vars`, adds only the new key with `set_env_var` (never wipes others), and reminds you to redeploy. > **"Give this service 512MB and half a core"** — Claude calls `set_service_resources` (reads current limits and merges your change) and reminds you to restart. > **"Persist /app/data and expose port 5432"** — Claude calls `create_mount` (named volume) and `create_port`, both applied on the next deploy. > **"My disk is full"** — Claude runs `get_storage_stats`, then `cleanup_docker_images` or `prune_docker` (with confirmation) to reclaim space. > **"Show me the Traefik dashboard config"** — for anything without a dedicated tool, Claude uses `easypanel_raw` to call the procedure directly. --- ## Available tools (57) | Category | Tools | |----------|-------| | **Projects** | `list_projects`, `get_project`, `create_project`, `delete_project` ⚠️ | | **Services** | `inspect_service`, `create_service`, `rename_service` ⚠️, `destroy_service` ⚠️, `deploy_service`, `start_service`, `stop_service` ⚠️, `restart_service`, `get_service_error`, `get_exposed_ports`, `get_service_notes`, `set_service_notes`, `set_service_resources` | | **Deploy / GitHub** | `set_source_github`, `set_source_image`, `enable_github_deploy`, `disable_github_deploy`, `list_actions`, `get_action` | | **Env Vars** | `get_env_vars`, `set_env_var`, `delete_env_var` ⚠️ | | **Logs** | `get_service_logs`, `get_build_logs`, `get_system_stats` | | **Containers** | `list_containers`, `exec_in_container` ⚠️, `get_docker_events` | | **Domains** | `list_domains`, `add_domain`, `remove_domain` ⚠️, `set_primary_domain` | | **Databases** | `create_database`, `inspect_database`, `destroy_database` ⚠️ | | **Volumes / Mounts** | `list_mounts`, `create_mount` ⚠️ | | **Ports** | `list_ports`, `create_port` ⚠️ | | **Compose** | `create_compose`, `inspect_compose`, `deploy_compose` | | **Monitoring** | `get_docker_stats`, `get_storage_stats`, `get_service_stats` | | **Maintenance** | `prune_docker` ⚠️, `cleanup_docker_images` | | **Server / Infra** | `list_users`, `list_certificates`, `list_nodes`, `restart_panel` ⚠️, `reboot_server` ⚠️ | | **Raw access** | `easypanel_raw` ⚠️ | ⚠️ = requires `confirm: "CONFIRMO"`. For `exec_in_container`, `create_mount`, `create_port` and `easypanel_raw` the confirmation is **conditional** (only for destructive commands, sensitive host-path bind mounts, privileged ports `< 1024`, and writes respectively). Full tool descriptions with parameters are in [`llms.txt`](llms.txt). For the underlying API (all generations), see [`docs/easypanel-api.md`](docs/easypanel-api.md). ### `easypanel_raw` — reach any of the ~375 operations Covering every Easypanel operation with a typed tool isn't practical, so anything without a dedicated tool is reachable directly: ```jsonc // read (default) — flat name, as documented in your panel's /api/openapi.json { "procedure": "listCertificates" } { "procedure": "getPanelDomain" } { "procedure": "listVolumeBackups", "input": { "projectName": "app", "serviceName": "api" } } // the old dot notation still works and is translated { "procedure": "certificates.listCertificates" } // write — requires isMutation:true AND confirm:"CONFIRMO" { "procedure": "setLogoSettings", "input": { /* ... */ }, "isMutation": true, "confirm": "CONFIRMO" } ``` Areas only reachable via `easypanel_raw`: Traefik, branding, Cloudflare Tunnel, Box, middlewares, notifications, volume/database backups, WordPress, storage providers, Docker builders, Git keys, cluster and update management. To discover names, read `GET /api/openapi.json`. The client classifies each operation against the panel's own OpenAPI spec, **fail-closed**: a read only executes if the spec says it's a read, so writes can't sneak past `readonly` mode or the confirmation gate — and the reverse is caught too (calling a read with `isMutation:true` is refused with a clear message). On **2.33+ that classification is exact**, since the public API declares GET for reads and POST for writes. On 2.31 it's the documented HTTP method; on 2.32, where the spec is POST-only and carries no such marker, the client falls back to the panel's naming convention (`get`/`list`/`inspect`/`check`/`query`/`search` = read, anything else = write), restricted to operations present in the spec. > One deliberate exception: on 2.33+ the panel validates query params **without type coercion**, so `?limit=5` arrives as the string `"5"` and is rejected. Whenever an input carries a non-string value, the client routes that read through the internal `/api/rpc` transport (which sends JSON in the body) and logs the reason to stderr. The read/write classification still comes from the spec first, so the guard is unaffected. --- ## Safety features ### Context banner Every response that touches a specific project/service starts with: ``` [Contexto ativo: projeto="my-project" | serviço="my-api"] ``` Claude always knows what it is modifying before taking any action. ### Confirmation guard Destructive or production-impacting actions return `BLOQUEADO` until they receive `confirm: "CONFIRMO"`: ```json { "status": "BLOQUEADO", "acao": "stop_service", "alvo": "serviço \"api\" (usuários perderão acesso)", "instrucao": "Para confirmar, passe o parâmetro: confirm: \"CONFIRMO\"", "aviso": "⚠️ Esta ação pode ser IRREVERSÍVEL. Confirme apenas se tiver certeza." } ``` This gates project/service deletion, stop/rename, env/domain removal, database destruction, the global server ops (`prune_docker`, `restart_panel`, `reboot_server`), and — conditionally — dangerous container commands, sensitive bind mounts, privileged ports and raw mutations. ### Read-only mode Set `MCP_ACCESS_MODE=readonly` to block **every** write at the source (`client.mutate`), covering both curated tools and `easypanel_raw`. Reads remain available — perfect for a production panel you only want to inspect. ### Raw escape-hatch controls `easypanel_raw` validates the operation name (flat or `namespace.procedure`, no path/query injection), requires the `input` to be an object (≤ 50KB), and demands `CONFIRMO` for any mutation. Set `EASYPANEL_RAW_DISABLED=1` to turn it off entirely. ### Secret redaction `list_users` strips `apiToken`, `twoFactorSecret` and password fields before returning — only `id`, `email`, `admin`, `twoFactorEnabled` and `createdAt` reach the model. ### Safe env vars (read-modify-write) `set_env_var` and `delete_env_var` read the current state, apply only the requested change, and write back. The Easypanel API replaces the entire env string on every update — without this protection it is easy to accidentally wipe all variables at once. ### Sensitive value masking `get_env_vars` masks values whose key matches `*SECRET*`, `*PASSWORD*`, `*TOKEN*`, `*KEY*` by default. Pass `include_values: true` to reveal. ### Token never leaks HTTP errors and WebSocket failures are logged to stderr and surfaced to the model as a generic message — the bearer token (sent in the WebSocket query string, as Easypanel requires) never reaches the model context. ### Input validation `projectName` / `serviceName` are validated against `^[a-z0-9][a-z0-9_-]*$` before being used to build a Docker service name or WebSocket query (defense-in-depth against target confusion / parameter injection). Ports are validated as integers `1–65535`; resource values must be positive numbers. --- ## Companion skill `/ep` Install the workflow skill for guided deploy operations in Claude Code: ```bash mkdir -p ~/.claude/skills/ep cp skill/SKILL.md ~/.claude/skills/ep/SKILL.md ``` Then use `/ep` for an interactive deploy workflow without needing to remember tool names. --- ## How it works The Easypanel panel talks to its backend over **tRPC** (`/api/trpc/.`), not a public REST API. This server uses the same endpoints: - **Reads** are tRPC queries; **writes** are tRPC mutations — see [`docs/easypanel-api.md`](docs/easypanel-api.md). - **Live logs, container exec and Docker events** use the panel's WebSocket channels (`/ws/serviceLogs`, `/ws/containerShell`, `/ws/dockerEvents`) — the same ones the UI uses — so they work without the licensed Advanced Logs (Loki). - A few input schemas (mounts, ports, resources) were **validated against a live Easypanel** and are documented in the API reference. --- ## Known limitations - **WordPress / Box service types** — not exposed as dedicated tools; reach them via `easypanel_raw` (e.g. `inspectWordPressService`, `createBoxService`). - **`easypanel_raw` reads bypass read-only mode** — read-only blocks writes only. A raw read can return sensitive data; use `EASYPANEL_RAW_DISABLED=1` in untrusted environments. - **Cluster tools** — `list_nodes` returns the local node only on single-server setups (no Swarm cluster). - **Docker events** are real-time only (no history) — an idle server may return an empty window. --- ## Testing without Claude ```bash npx @modelcontextprotocol/inspector dist/index.js ``` Opens a browser UI where you can call any tool manually and inspect the response. --- ## Comparison with similar packages | Feature | easypanel-mcp-server | easypanel-mcp (sitp2k) | |---------|---------------------|----------------------| | Curated tools | **57** | ~15 | | Raw access to all ~375 API operations | ✅ (`easypanel_raw`) | ❌ | | Auth method | Bearer token | Email + password | | Confirmation guard | ✅ | ❌ | | Read-only mode | ✅ | ❌ | | Container exec + live logs (WebSocket) | ✅ | ❌ | | Volumes / ports / compose / resources | ✅ | ❌ | | Server maintenance (prune / reboot) | ✅ | ❌ | | Safe env update (read-modify-write) | ✅ | ❌ | | Secret redaction & value masking | ✅ | ❌ | | Companion Claude skill | ✅ | ❌ | | Known limitations documented | ✅ | ❌ | --- ## 🤝 Contributing Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for how to add tools, report bugs and open PRs. - [Bug report](.github/ISSUE_TEMPLATE/bug_report.md) - [Feature request](.github/ISSUE_TEMPLATE/feature_request.md) --- ## 👤 Author Created by **[Helbert Paranhos](https://github.com/helbertparanhos)** from **[Strat Academy](https://stratacademy.com.br)**. [![Instagram](https://img.shields.io/badge/@helbertparanhos-E4405F?style=for-the-badge&logo=instagram&logoColor=white)](https://www.instagram.com/helbertparanhos) [![YouTube](https://img.shields.io/badge/stratacademy-FF0000?style=for-the-badge&logo=youtube&logoColor=white)](https://www.youtube.com/@stratacademy) [![LinkedIn](https://img.shields.io/badge/helbert--paranhos-0077B5?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/helbert-paranhos/) [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-FFDD00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black)](https://buymeacoffee.com/helbertparanhos) If this project was useful, consider giving it a ⭐ and following [Strat Academy](https://stratacademy.com.br) for more AI automation content. --- ## 📄 License MIT © [Helbert Paranhos](https://github.com/helbertparanhos) / [Strat Academy](https://stratacademy.com.br) See [LICENSE](LICENSE) for details.