easypanel-mcp-server
# easypanel-mcp-server
> MCP Server for full Easypanel control via Claude Code, Cursor and Claude Desktop.
[](https://www.npmjs.com/package/easypanel-mcp-server)
[](LICENSE)
[](https://github.com/helbertparanhos/easypanel-mcp-server/stargazers)
[](https://github.com/helbertparanhos/easypanel-mcp-server/network/members)
[](https://github.com/helbertparanhos/easypanel-mcp-server/issues)
[](https://github.com/helbertparanhos/easypanel-mcp-server/actions/workflows/ci.yml)
[](https://glama.ai/mcp/servers/helbertparanhos/easypanel-mcp-server)
[](https://www.typescriptlang.org/)
[](https://nodejs.org/)
[](https://modelcontextprotocol.io/)
[](https://claude.ai/code)
[](https://cursor.sh)
[](https://claude.ai/download)
[](https://www.instagram.com/helbertparanhos)
[](https://www.youtube.com/@stratacademy)
[](https://www.linkedin.com/in/helbert-paranhos/)
[](https://buymeacoffee.com/helbertparanhos)
[](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/<operation>`, 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 <your-panel>/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/<router>.<procedure>`), 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)**.
[](https://www.instagram.com/helbertparanhos)
[](https://www.youtube.com/@stratacademy)
[](https://www.linkedin.com/in/helbert-paranhos/)
[](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.
TDQS
Scored across 57 tools
Most tools are clearly scoped by resource and action, but there are real overlaps: deploy_service already handles compose while deploy_compose also exists, and stats/port/log cleanup tools have close boundaries. The descriptions help, but an agent could still pick the wrong tool in several situations.
Names overwhelmingly follow a snake_case verb_noun pattern with consistent list/get/create/set/delete/destroy prefixes. Minor deviations include easypanel_raw being noun-first and the mix of get_project, inspect_service, and inspect_compose, but the overall style is predictable.
57 tools is far above the comfortable agent-facing range and makes selection and context usage heavy. Although the Easypanel domain is broad and most tools are individually purposeful, several could be consolidated or left to easypanel_raw to reduce surface area.
Core workflows like project/service lifecycle, env vars, domains, logs, and deploys are well covered. However, there is no dedicated set_compose_file tool, mounts and ports cannot be removed directly, and most admin operations are read-only, so agents must fall back to easypanel_raw for notable gaps.