The Panelica MCP Server (package panelica-mcp) is an open-source Model Context Protocol server that turns your Panelica panel into a set of tools an AI assistant can call directly — list and create accounts, provision domains, manage DNS, issue SSL certificates, create databases and mailboxes, run backups, inspect Docker containers, and check server health — through the same Panelica External API your panel UI already uses. It runs as a small stdio process next to your MCP client (Claude Code, Claude Desktop, Cursor, OpenAI's Codex CLI, Continue.dev, Cline, Zed, or any other stdio MCP client), signs every request to your panel with HMAC-SHA256, and never sends your API secret anywhere but your own panel. As of September 2026, version 0.5.1 ships 404 tools across 51 categories, with a 37-tool core set registered by default.
This thread is the reference: every client's setup steps, the full security model, the scope table, 20+ example prompts, a real measured agent session, and troubleshooting. If you just want the two-minute version, see the announcement thread and the "Connect Claude Code in 60 seconds" thread linked at the bottom.
It is a thin, stateless adapter — no database of its own, no telemetry, nothing cached to disk. What it does:
- Your MCP client launches the
binary over stdio.Code:
panelica-mcp - At startup, the server fetches
from your panel and builds the tool list from it — this is the live catalogue, and it is the default. If the panel is unreachable, it falls back to a bundled snapshot (Code:
<PANELICA_BASE_URL>/v1/api-specforces the snapshot always).Code:PANELICA_LIVE_SPEC=0 - The client asks for the tool list and gets it back together with server instructions — how ids work, how scopes work, what the response envelope looks like, what to do with each error class, and workflow recipes — all generated from that same catalogue.
- When the client calls a tool, the server builds the matching HTTP request, signs it with HMAC-SHA256 using your local
, and sends it to your panel's External API.Code:
PANELICA_API_SECRET - The HTTP response comes back to the client as the tool result. A failed call is returned as an explanation of what to do next — missing scope, wrong id, rate-limit reset — not a bare status code.
The transport chain looks like this:
Code:
MCP client (Claude, Cursor, Codex...)
| stdio JSON-RPC
v
panelica-mcp (this package, runs on your machine or in Docker)
| HTTPS + HMAC-SHA256 (X-API-Key, X-Timestamp, X-Signature)
v
https://your-panel-host:8443/api/external/v1/...
| nginx on the panel host (TLS + strips /api/external prefix)
v
127.0.0.1:3002 external-server (verifies the HMAC signature)
|
v
Panelica panel + the Linux services it manages
It is exactly as safe as the API key you hand it, which is the point of scoping:
- Nothing is preselected. Creating an API key in the panel starts with a blank scope list and a live search box over 50 scopes — you decide exactly what this assistant can touch.
- HMAC-SHA256 signed requests. Every call is signed over METHOD + PATH + QUERY + TIMESTAMP + BODY with your secret. The panel rejects anything whose timestamp drifts more than 5 minutes, so replays don't work.
- The secret never leaves your machine. It's read from the process environment, used only to compute the signature, never logged and never written to disk.
- RBAC on top of scopes. The key inherits its owner's role. A USER-role key only ever sees that user's own domains, databases, mail, etc. Some routes are ROOT/ADMIN only regardless of scope (access logs, for example).
- Full audit trail. Every call the assistant makes hits the panel's normal audit log and is indistinguishable from any other authenticated API call — traceable to the exact key that made it.
- Rate limited. Keys carry a tier (60 to unlimited requests/minute — table below), so a runaway loop can't hammer the panel.
- No data harvesting. No telemetry, no cache, no third party ever sees your traffic.
The practical advice: start read-only, add write scopes only for the task at hand, and reserve destructive scopes (
Code:
*:delete
Code:
accounts:delete
Code:
domains:delete
Code:
backups:restore
- A running Panelica panel — version 1.0.193 or newer recommended; the External API surface is stable from 1.0.180+.
- HTTPS access to the panel UI on port 8443 from whichever machine will run
— the same port you already use in a browser, no extra firewall change needed.Code:
panelica-mcp - Either Node.js ≥ 20 (for the npm path) or Docker (for the container path) on that machine.
You do not need to install anything on the panel host itself, and you do not need to open the internal port 3002 to the public internet.
- Sign in to the panel as root or any account with permission to manage API keys.
- Go to Settings → API Keys → Generate API Key.
- Pick the scopes the assistant needs from the live-search list. For a read-only assistant,
is enough on panel 1.0.528+ (older panels need the individualCode:
*:readscopes). For full automation, grantCode:<area>:readandCode:*:writetoo, orCode:*:delete. Every tool's description in this server states the scopes it requires.Code:*:* - Copy both the key (
) and the secret (Code:
pk_...). The secret is shown only once — put it in a password manager, not a chat log.Code:sk_...
Before wiring up a client, prove the credentials work end to end:
Bash:
export PANELICA_BASE_URL=https://your-panel-host:8443/api/external
export PANELICA_API_KEY=pk_xxxxxxxx
export PANELICA_API_SECRET=sk_xxxxxxxx
TS=$(date +%s)
# Signature is over METHOD + PATH + TIMESTAMP + BODY. The path is the
# backend-visible path (/v1/...), NOT the /api/external/ prefix nginx
# strips before forwarding — panelica-mcp does this automatically.
SIG=$(printf "GET/v1/api-keys${TS}" \
| openssl dgst -sha256 -hmac "$PANELICA_API_SECRET" -hex | awk '{print $2}')
curl -sk "$PANELICA_BASE_URL/v1/api-keys" \
-H "X-API-Key: $PANELICA_API_KEY" \
-H "X-Timestamp: $TS" \
-H "X-Signature: $SIG"
You should get back JSON listing your API keys. If you get a 401 instead, jump to Troubleshooting below.
| Scenario | Recommended PANELICA_BASE_URL |
|---|---|
| MCP client on your laptop, panel on a remote server |
Code:
|
| MCP client and panel on the same machine |
Code:
|
Do not open port 3002 to the public internet — it should be reached only through the 8443 reverse proxy or from
Code:
127.0.0.1
One command:
Bash:
claude mcp add panelica \
-e PANELICA_BASE_URL=https://your-panel:8443/api/external \
-e PANELICA_API_KEY=pk_... \
-e PANELICA_API_SECRET=sk_... \
-- npx -y panelica-mcp
Ask it to "list my domains" and watch it pick up the tool.
Edit the config file:
- macOS:
Code:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
Code:
%APPDATA%\Claude\claude_desktop_config.json - Linux (beta):
Code:
~/.config/Claude/claude_desktop_config.json
JSON:
{
"mcpServers": {
"panelica": {
"command": "npx",
"args": ["-y", "panelica-mcp"],
"env": {
"PANELICA_BASE_URL": "https://your-panel-host:8443/api/external",
"PANELICA_API_KEY": "pk_...",
"PANELICA_API_SECRET": "sk_..."
}
}
}
}
Save, fully quit Claude Desktop (not just close the window) and reopen it. A new chat shows
Code:
panelica
Code:
PANELICA_TOOLSETS=all
Settings → MCP → Add new server:
JSON:
{
"panelica": {
"command": "npx",
"args": ["-y", "panelica-mcp"],
"env": {
"PANELICA_BASE_URL": "https://your-panel-host:8443/api/external",
"PANELICA_API_KEY": "pk_...",
"PANELICA_API_SECRET": "sk_..."
}
}
}
Cursor caps active tools at roughly 40 across all connected MCP servers and silently drops the rest — this is exactly why the default toolset here is a 37-tool core set instead of dumping all 404 tools on every client.
Add to
Code:
~/.codex/config.toml
Code:
mcp_servers.panelica]
command = "npx"
args = ["-y", "panelica-mcp"]
[mcp_servers.panelica.env]
PANELICA_BASE_URL = "https://your-panel:8443/api/external"
PANELICA_API_KEY = "pk_..."
PANELICA_API_SECRET = "sk_..."
Any MCP-aware editor that accepts a stdio command works the same way — give it
Code:
npx -y panelica-mcp
Code:
dist/index.js
Docker, if you'd rather not touch Node.js on the host:
Bash:
docker pull ghcr.io/panelica/panelica-mcp:latest
JSON:
{
"command": "docker",
"args": [
"run", "--rm", "-i",
"-e", "PANELICA_BASE_URL",
"-e", "PANELICA_API_KEY",
"-e", "PANELICA_API_SECRET",
"ghcr.io/panelica/panelica-mcp:latest"
],
"env": {
"PANELICA_BASE_URL": "https://your-panel-host:8443/api/external",
"PANELICA_API_KEY": "pk_...",
"PANELICA_API_SECRET": "sk_..."
}
}
Code:
-i
Code:
--rm
Building from source, if you'd rather read the code first:
Bash:
git clone https://github.com/Panelica/panelica-mcp.git
cd panelica-mcp
npm install
npm run build
node dist/index.js
Full source and issue tracker: https://github.com/Panelica/panelica-mcp. Package: https://www.npmjs.com/package/panelica-mcp. It's also listed in the official MCP Registry as
Code:
io.github.Panelica/panelica-mcp
MCP clients budget tools — Cursor's ~40-tool cap is the tightest, and every registered tool costs prompt tokens on every turn. So by default the server registers a compact core set: 34 everyday tools (accounts, domains, DNS, SSL, databases, e-mail, FTP, backups, server status/services, WordPress, Docker, plans) plus 3 meta tools that reach the whole catalogue.
| Tool | What it does |
|---|---|
|
Code:
| Keyword search over all 404 tools; understands plain-language terms — website→domain, certificate→ssl, mailbox→email, container→docker — and stems plurals |
|
Code:
| One tool in full: every parameter with its provenance, body fields (type, required, enum, default), and the response envelope |
|
Code:
| Runs any catalogue tool by name with its arguments, through the same scoped, HMAC-signed client |
Choose the set with
Code:
PANELICA_TOOLSETS
| Value | Registered tools |
|---|---|
|
Code:
| 34 + 3 meta = 37 |
|
Code:
| Every catalogue tool + 3 meta (all 404 — fine for Claude Code / Claude Desktop, which don't have Cursor's cap) |
|
Code:
| Only the 3 meta tools |
| Category slugs, e.g.
Code:
| Just those categories (plus
Code:
|
A direct call to a tool that isn't registered still works — a client that learned the name from
Code:
panelica_find_tools
An assistant that only sees 400 tool names guesses ids, retries 403s, and misreads responses. This server hands it the API "by heart" instead:
- Your key, up front. At startup the server calls
once and tells the model which key it holds, its scopes, tier and expiry — a read-only key is announced as read-only, and out-of-scope tools are declined with the scope to add instead of being attempted. A rejected credential is reported before the first tool call. (Code:
GET /v1/medisables this.)Code:PANELICA_STARTUP_PROBE=0 - Scopes as the code enforces them. Newer panels derive required scopes from the middleware chain rather than hand-written docs — including "one of A or B" rules and ROOT/ADMIN role limits — and the tool description repeats them exactly.
- Allowed values, not guesses. Fields with a fixed value list —
,Code:
ssl_provider, database userCode:web_server, DNS recordCode:role, accountCode:type— carry a JSON-schema enum and default.Code:role - Big lists stay usable. Every list-returning GET accepts
,Code:
_limit(comma list) andCode:_fields(case-insensitive substring); the server applies them and reportsCode:_match, so a panel with hundreds of domains can be searched in one call.Code:{total, matched, shown} - Validation errors name the field. A Gin validation failure becomes
, using the JSON field name the model already used.Code:
field "user_id" is required - Search speaks your language.
maps website→domain, certificate→ssl, mailbox→email, container→docker, and stems plurals.Code:
panelica_find_tools - Transient limits are absorbed. A 429 whose window resets within 15 seconds is waited out once automatically; a GET that fails on the network is retried once; a clock skew above two minutes against the panel is flagged (HMAC timestamps are rejected beyond ±5 minutes).
- Every tool description carries the HTTP route, required scopes, a
line with response fields, and a risk class — read-only, mutating, or destructive.Code:
Returns: - Id parameters say where they come from — "UUID of the domain — obtain it from GET /v1/domains" — for path, query and body fields alike.
- Errors are explained, not echoed. A 403 names the missing scope and says retrying won't help; a 404 says to re-list and use a real id; a 429 reports the reset window; a 5xx says to report it, not loop.
- Results are bounded. Oversized list responses are cut to the first items with an explicit
note (still valid JSON) instead of flooding the model's context (Code:
_truncated, default 60000).Code:PANELICA_MAX_RESULT_CHARS - MCP safety annotations.
on GETs andCode:
readOnlyHinton DELETEs — capable clients like Cursor and Codex use these to auto-approve reads and ask before writes/deletes.Code:destructiveHint - Resources for clients that support them:
(the instructions),Code:
panelica://guide(every tool with route and summary), and in live modeCode:panelica://catalogue(your panel's full API spec).Code:panelica://spec
Real prompts that work today, grouped by job, with the tool(s)/scope(s) behind each:
Accounts (
Code:
accounts:read
Code:
accounts:write
- "List every account on this panel and which plan each is on." —
Code:
panelica_accounts_get_v1_accounts - "Create a new account for [email protected] on the professional plan." —
Code:
panelica_accounts_post_v1_accounts - "Suspend the account for [email protected]." —
Code:
panelica_accounts_post_v1_accounts_id_suspend
Domains (
Code:
domains:read
Code:
domains:write
- "Which domains exist on this panel and what PHP version does each use?" —
Code:
panelica_domains_get_v1_domains - "Add shop.example.com to account X and set it to PHP 8.3." —
then the domain's PHP PATCH toolCode:
panelica_domains_post_v1_domains - "List the subdomains under shop.example.com." — the domain's subdomains GET tool
DNS (
Code:
dns:read
Code:
dns:write
Code:
dns:delete
- "List the DNS records for shop.example.com and tell me whether it has an SPF record." — the zone records GET tool
- "Add a CNAME www → shop.example.com and an A record mail → 203.0.113.10." — the zone records POST tool
- "Create a TXT record _mcp-test with content mcp-ok on shop.example.com, verify it exists, then delete it." — POST the record, GET the zone to verify, DELETE the record
SSL (
Code:
ssl:read
Code:
ssl:write
- "Show the SSL certificate status for shop.example.com: issuer, expiry, auto-renew." — the SSL status GET tool
- "List every domain whose certificate expires in the next 14 days and renew them all." — SSL status GET, then the issue POST tool per match
- "Which values are allowed for ssl_provider and web_server on this panel?" — answered straight from the schema without a call (letsencrypt / self_signed / none default letsencrypt; nginx_apache / nginx_only default nginx_apache)
Email (
Code:
email:read
Code:
email:write
- "How many email accounts exist across all domains?" —
Code:
panelica_email_get_v1_email-accounts - "Create a mailbox [email protected]." —
Code:
panelica_email_post_v1_email-accounts
Databases (
Code:
databases:read
Code:
databases:write
- "How many databases exist and which is the largest?" —
Code:
panelica_databases_get_v1_databases - "Create a MySQL database for shop.example.com with a dedicated user." —
Code:
panelica_databases_post_v1_databases
Backups (
Code:
backups:read
Code:
backups:write
- "Show the backup schedule and the last successful backup for account X." — the backups GET tool
- "Kick off a full backup for shop.example.com right now." — the backups POST tool
WordPress
- "Which WordPress sites are installed, and are any plugin or core updates pending?" —
Code:
panelica_wordpress_get_v1_wordpress - "Update WordPress core on shop.example.com." —
Code:
panelica_wordpress_post_v1_wordpress_id_update-core
Code:
panelica_describe_tool
Docker (
Code:
docker:read
- "List every running container and how much memory each is using." —
Code:
panelica_docker_get_v1_docker_containers - "Which containers belong to account X?" — same tool, filtered
Server health (
Code:
server:read
Code:
services:restart
- "Give me a short server health summary: status, running services, load and memory." —
,Code:
panelica_server_get_v1_server_status,Code:_servicesCode:_metrics - "Restart the mysql service." —
Code:
panelica_server_post_v1_server_services_name_restart
The assistant only ever calls tools whose scopes your key actually has — a read-only key safely answers every "list" and "show" question here and declines every "create" / "delete" one, naming the scope it's missing.
We ran OpenAI's Codex CLI 0.154 against a dev panel through this server: 14 tasks across two rounds, zero invented ids — every id it used came from a list call first. Before any write it called
Code:
panelica_describe_tool
Code:
dns
Code:
domains:write
Code:
_mcp-test
Code:
mcp-ok
Code:
_fields
Code:
_match
Code:
_limit
Code:
ssl_provider
Code:
web_server
A full transcript of a similar session with Claude Code — creating a domain and issuing an SSL certificate end to end — is posted in the "Show & Tell" thread linked below.
No scope is preselected when you create a key — the create dialog has live search over all 50. Every family also accepts its wildcard (e.g.
Code:
domains:*
Code:
*:*
| Area | Scopes |
|---|---|
| Accounts |
Code:
Code:
Code:
|
| Domains & subdomains |
Code:
Code:
Code:
|
| Databases |
Code:
Code:
Code:
|
| DNS |
Code:
Code:
Code:
|
|
Code:
Code:
Code:
| |
| FTP |
Code:
Code:
Code:
|
| SSL |
Code:
Code:
|
| Backups & snapshots |
Code:
Code:
Code:
|
| File Manager |
Code:
Code:
Code:
|
| CloudFlare |
Code:
Code:
Code:
|
| Docker & app templates |
Code:
Code:
Code:
|
| App hosting (Laravel / Node.js / Python) |
Code:
Code:
Code:
|
| Git & Deploy |
Code:
Code:
Code:
|
| Logs & audit |
Code:
Code:
|
| Security (antivirus, firewall, IP blocks) |
Code:
Code:
Code:
|
| Server & infrastructure |
Code:
Code:
|
| Service control |
Code:
Code:
Code:
|
| Plans |
Code:
Code:
|
| Webhooks |
Code:
Code:
Code:
|
| Bandwidth |
Code:
|
| License |
Code:
|
| Migrations (panel-to-panel) |
Code:
|
| Terminal |
Code:
|
| Full access |
Code:
|
Mutating service control deliberately requires its own action scopes (or
Code:
server:write
Code:
server:read
API keys carry a rate-limit tier chosen when you create the key:
| Tier | Requests / minute |
|---|---|
| starter (default) | 60 |
| professional | 300 |
| business | 1000 |
| enterprise | unlimited |
Responses carry
Code:
X-RateLimit-Remaining-Minute
Code:
X-RateLimit-Reset-Minute
Code:
starter
Code:
429 RATE_LIMIT_EXCEEDED
- HMAC-SHA256 request signing over METHOD + PATH + QUERY + TIMESTAMP + BODY.
- Secrets stay local — read from the environment, used only for signing, never logged or written to disk.
- Scope-restricted keys — one key per use case, only the scopes that use case needs.
- Full audit trail — every call is a normal authenticated API call in the panel's audit log, traceable to the key.
- No data harvesting — no telemetry, no cache, no third party sees your traffic.
- Container hardening — the Docker image runs as a non-root user and exposes no ports; it only speaks stdio.
- SSRF prevention — private/reserved IPs are rejected for A/AAAA DNS records.
- FTP accounts are fenced to the owning account's home directory; passwords must be at least 8 characters.
- Keys can be IP-whitelisted and given an expiry (
days) at creation, and revoked instantly from the panel at any time.Code:
expires_in
| Symptom | Likely cause | Fix |
|---|---|---|
| Client reports "0 tools available" | Server crashed at startup — usually a missing env var | Run
Code:
|
|
Code:
|
Code:
| Re-check the client config; restart the client after editing |
|
Code:
| Wrong
Code:
| Check NTP sync on both the MCP host and the panel host |
|
Code:
| Local clock drift > 5 min | Sync NTP on the MCP host |
| Connect timeout on BASE_URL | Wrong host/port — usually
Code:
|
Code:
Code:
|
| Tool result starts with "Panelica API error 403" and names a scope | Your key lacks that scope | Add it in Settings → API Keys — the assistant is told not to retry |
| Tool result says "404 … re-list" | The assistant used an id that doesn't exist for this key's owner | Nothing to fix server-side — it will re-list and pick a real id |
| "Schema not statically declared" | The endpoint binds a dynamic body (map/multipart) | Pass a free-form
Code:
|
| Startup log: "clock skew of Ns versus the panel" | The MCP host's clock is off | Sync NTP; signed requests carry a timestamp the panel checks |
| Startup log: "startup credential check failed" | Key/secret rejected by
Code:
| Fix the key in the panel and restart the client |
| TLS verification fails | Panel is using its self-signed certificate | Install a real cert (panel UI → Settings → SSL) — don't disable verification client-side |
Still stuck? Open an issue at https://github.com/Panelica/panelica-mcp/issues with the (redacted) stderr output, or post here.
Do I need to change anything on the panel itself? No. Only an API key with the scopes you choose. The External API has been part of every Panelica panel since 1.0.180.
Will new panel features show up automatically? Yes — in live mode (the default) the server pulls your panel's own
Code:
/v1/api-spec
Code:
panelica-mcp
Can I point one MCP server config at more than one panel? No — each
Code:
panelica-mcp
Code:
PANELICA_BASE_URL
Does it work with anything besides Claude, Cursor and Codex CLI? Any MCP-compatible client works the same way — three env vars and a stdio command. This guide covers Claude Code, Claude Desktop, Cursor and Codex CLI specifically because their config formats differ; Continue.dev, Cline, Zed, Gemini CLI and others connect identically.
What happens if I don't set PANELICA_TOOLSETS? It defaults to
Code:
core
Can the assistant see other accounts' data? No. The key inherits its owner's role and the panel's RBAC — a USER-role key only ever sees that user's own domains, databases, mail and files.
Does it cache or store anything? No — stateless adapter, no telemetry, nothing written to disk.
My panel uses a self-signed certificate — do I disable TLS verification client-side? Don't. Install a real certificate on the panel (Settings → SSL) instead.
How do I know exactly which scope a specific tool needs? Ask the assistant to run
Code:
panelica_describe_tool
What's the difference between calling a tool directly and using panelica_call? They hit the same scoped, signed HTTP client —
Code:
panelica_call
Code:
panelica_find_tools
- GitHub: https://github.com/Panelica/panelica-mcp
- npm: https://www.npmjs.com/package/panelica-mcp
- Docker image:
Code:
ghcr.io/panelica/panelica-mcp - MCP Registry entry:
Code:
io.github.Panelica/panelica-mcp - Announcement: see "Panelica MCP Server 0.5.1: connect Claude, Cursor, Codex and Gemini to your panel" in Announcements
- Quick start: "How to: Connect Claude Code to your Panelica server in 60 seconds" in this section
- Real transcript: "Show & Tell: I asked Claude to create a site and issue SSL — full transcript"
- Scope cheat-sheet: "Which permissions should you give an AI assistant?"
- For the panel's own built-in AI desktop (runs inside the panel instead of on your machine), see the OpsAI guide in General Discussion.
Questions, edge cases, and "it did something unexpected" reports are all welcome below.