Skip to main content
Glama
README.md
# mcp-1panel-plus

**Unified Multi-Node Model Context Protocol (MCP) Server for 1Panel v2** — control **both** your **1Panel Main Node (`"local"`)** and all connected **X-Pack Child Nodes (`nodeId`)** from a single MCP connection.

[![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](./LICENSE)
[![Node.js >= 20](https://img.shields.io/badge/node-%3E%3D20.0.0-brightgreen.svg)](https://nodejs.org/)

While upstream [`1Panel-dev/mcp-1panel`](https://github.com/1Panel-dev/mcp-1panel) stops at 20 read/basic-create tools, **`mcp-1panel-plus`** covers the complete day-2 self-hosting lifecycle across every node in a 1Panel v2 cluster (39 tools):

- **End-to-End Reverse-Proxy Websites & HTTPS**: Create proxy/static websites, list/create/edit/delete upstream reverse-proxy rules (`Host`, TLS SNI, headers, caching, sub-filters), **bind existing or ACME SSL certificates to enable HTTPS / HSTS / HTTP/3**, and start/stop/delete sites on any node.
- **App Store Updates & Lifecycle**: Sync remote App Store catalogs across main & child nodes, inspect available app upgrade versions, upgrade installed apps (with automatic pre-upgrade backup), and `start` / `stop` / `restart` / `rebuild` apps.
- **Multi-Node 1Panel Self-Upgrade & Rollback**: Check available 1Panel releases against Main & Child versions, upgrade the Main Node (`1panel-core` + `1panel-agent` with automatic restart-reconnect polling), upgrade X-Pack Child Nodes (enforcing Main-before-Child ordering), inspect upgrade logs, and trigger child-node rollback.
- **Docker Container Operations**: List all containers (including custom Compose & standalone containers outside the App Store), `start` / `stop` / `restart` containers, and fetch bounded, ANSI-cleaned, secret-scrubbed container logs.
- **Built-in Auto-Reconnecting SSH Loopback Tunnel (`PANEL_SSH_HOST`)**: Connect locally to `127.0.0.1:20005` over SSH without exposing 1Panel's API port publicly, fighting CDN/WAF timeouts, or losing the MCP process when `1panel-core` restarts during an upgrade.

---

## Architecture

```mermaid
flowchart LR
  Client["AI Client\n(Claude Code / Cursor / Codex / T3 Code)"]
  MCP["mcp-1panel-plus\n(Local Process • stdio or HTTP)"]
  Tunnel["Auto-Reconnecting SSH Tunnel\n127.0.0.1:ephemeral -> 127.0.0.1:20005\n(or Direct HTTPS)"]
  Main["1Panel Main Node\nCurrentNode: local\n(1panel-core + 1panel-agent)"]
  Child1["1Panel Child Node #1\nCurrentNode: <resolved-name>\n(1panel-agent mTLS)"]
  ChildN["1Panel Child Node #N\nCurrentNode: <resolved-name>\n(1panel-agent mTLS)"]

  Client <-->|"MCP (stdio)"| MCP
  MCP -->|"1Panel-Token (MD5)\n+ CurrentNode Header"| Tunnel
  Tunnel --> Main
  Main <-->|"X-Pack mTLS"| Child1
  Main <-->|"X-Pack mTLS"| ChildN
```

### Security & Reliability Design
1. **Safe Multi-Node Routing (`CurrentNode`)**: Every node-scoped tool accepts `nodeId` (`"local"` default, or a numeric string ID like `"3"` from `list_nodes`). Child node names are resolved dynamically from `/api/v2/core/nodes/list` — caller strings are **never** interpolated into `CurrentNode`, and missing/offline child nodes **never** silently fall back to `"local"`.
2. **Nginx Directive Injection Guard**: Upstream URLs (`proxy_address` / `proxy_pass`), location paths, and headers are validated against whitespace, control/format characters, and Nginx directive syntax (`;{}'"\$#`).
3. **Defense-in-Depth Secret Redaction**: Private keys, PEM certificate bodies, database passwords, SSH credentials, X-Pack licenses, `docker-compose.yml` contents, and app `.env` maps are stripped from every tool output.
4. **Zero Auto-Retry on Mutations**: Write operations are never retried on network drop or HTTP 5xx; uncertain outcomes are surfaced explicitly so the agent inspects state before acting again.

---

## Quick Start

### Prerequisites
1. **Node.js `>= 20`** (`v22` or `v24` recommended) and `pnpm` / `npm`.
2. **1Panel v2 API Key**: Enable **Panel Settings -> Security -> API Interface** on your 1Panel Main Node and copy the 32-character API Key.
   - *Tip*: When using `PANEL_SSH_HOST`, you can lock 1Panel's **IP Whitelist (`AllowIPs`)** to `127.0.0.1` so the API Key cannot be used from the public internet.
   - *Domain Binding Note*: If your 1Panel has **Domain Binding (`BindDomain`)** enabled, also set `PANEL_BIND_DOMAIN=panel.example.com` so loopback requests send the expected HTTP `Host` header.

### Build from Source

```bash
git clone https://github.com/EC061/mcp-1panel-plus.git
cd mcp-1panel-plus
pnpm install
pnpm test
```

---

## MCP Client Configuration

### 1. Recommended: SSH Loopback Mode (`stdio`)

Works identically in **Claude Code** (`~/.claude.json`), **Cursor** (`~/.cursor/mcp.json`), **Claude Desktop**, and **T3 Code**:

```json
{
  "mcpServers": {
    "1panel-plus": {
      "command": "npx",
      "args": ["-y", "github:EC061/mcp-1panel-plus"],
      "env": {
        "PANEL_SSH_HOST": "my-1panel-main",
        "PANEL_REMOTE_PORT": "20005",
        "PANEL_ACCESS_TOKEN": "your_32_char_1panel_api_key",
        "PANEL_BIND_DOMAIN": "panel.example.com",
        "PANEL_ACCESS_LEVEL": "readwrite"
      }
    }
  }
}
```

Or pointing directly to a local clone (`dist/index.js`) for instant zero-network startup:

```json
{
  "mcpServers": {
    "1panel-plus": {
      "command": "node",
      "args": ["/absolute/path/to/mcp-1panel-plus/dist/index.js"],
      "env": {
        "PANEL_SSH_HOST": "my-1panel-main",
        "PANEL_REMOTE_PORT": "20005",
        "PANEL_ACCESS_TOKEN": "your_32_char_1panel_api_key",
        "PANEL_BIND_DOMAIN": "panel.example.com",
        "PANEL_ACCESS_LEVEL": "readwrite"
      }
    }
  }
}
```

### 2. Codex (`~/.codex/config.toml`)

```toml
[mcp_servers.1panel_plus]
command = "node"
args = ["/absolute/path/to/mcp-1panel-plus/dist/index.js"]

[mcp_servers.1panel_plus.env]
PANEL_SSH_HOST = "my-1panel-main"
PANEL_REMOTE_PORT = "20005"
PANEL_ACCESS_TOKEN = "your_32_char_1panel_api_key"
PANEL_BIND_DOMAIN = "panel.example.com"
PANEL_ACCESS_LEVEL = "readwrite"
```

### 3. Direct HTTPS Mode (Without SSH)

```json
{
  "mcpServers": {
    "1panel-plus": {
      "command": "node",
      "args": ["/absolute/path/to/mcp-1panel-plus/dist/index.js"],
      "env": {
        "PANEL_HOST": "https://panel.example.com",
        "PANEL_ACCESS_TOKEN": "your_32_char_1panel_api_key",
        "PANEL_ACCESS_LEVEL": "readwrite"
      }
    }
  }
}
```

### 4. Docker & Streamable HTTP Mode

```bash
docker build -t mcp-1panel-plus .

# Run as Streamable HTTP on 127.0.0.1:8000/mcp with Bearer authentication
docker run --rm -p 127.0.0.1:8000:8000 \
  -e PANEL_HOST="https://panel.example.com" \
  -e PANEL_ACCESS_TOKEN="your_32_char_1panel_api_key" \
  -e PANEL_ACCESS_LEVEL="readonly" \
  -e MCP_TRANSPORT="http" \
  -e MCP_HTTP_ADDR="0.0.0.0:8000" \
  -e MCP_ALLOW_REMOTE="true" \
  -e MCP_AUTH_TOKEN="replace_with_random_bearer_token" \
  mcp-1panel-plus
```

---

## Access Levels (`PANEL_ACCESS_LEVEL` / `--access-level`)

Tools are filtered from `tools/list` **and** enforced again on `tools/call`:

| Level | Tools Exposed | Permitted Operations |
| :--- | :---: | :--- |
| `readonly` | **23** | Node discovery, OS/dashboard telemetry, website/proxy/HTTPS/SSL metadata, installed app & upgrade checks, panel release checks, container list & logs, MySQL/PostgreSQL inspection |
| `readwrite` *(default)* | **33** | Everything in `readonly` **plus** `create_website`, `enable_website_https`, `upsert_website_proxy`, `delete_website_proxy`, `operate_website`, `create_ssl`, `sync_app_store`, `operate_installed_app` (`upgrade`/`restart`/`start`/`stop`/`rebuild`), `operate_container`, `create_database` |
| `full` *(or `admin`)* | **39** | Everything in `readwrite` **plus** `delete_website`, `install_mysql`, `install_openresty`, `upgrade_main_panel`, `upgrade_child_node`, `rollback_child_node` |

---

## Complete Tool Catalog (39 Tools)

### System & Multi-Node Discovery (3)
| Tool | Access | Scope | Description |
| :--- | :---: | :---: | :--- |
| `list_nodes` | `readonly` | Main | List `"local"` and all X-Pack Child Node IDs, statuses, and versions |
| `get_system_info` | `readonly` | `nodeId` | OS, platform, kernel version/architecture, total disk bytes |
| `get_dashboard_info` | `readonly` | `nodeId` | Resource counts + live CPU, load, RAM, swap, network, and disk usage |

### Websites, Reverse Proxy & HTTPS (11)
| Tool | Access | Scope | Description |
| :--- | :---: | :---: | :--- |
| `list_websites` | `readonly` | `nodeId` | Search/list websites on the selected node |
| `get_website` | `readonly` | `nodeId` | Read safe website metadata by `website_id` |
| `list_website_domains` | `readonly` | `nodeId` | List domains & ports bound to `website_id` |
| `get_website_https` | `readonly` | `nodeId` | Inspect HTTPS, TLS versions, HSTS, HTTP/3, and bound cert metadata |
| `create_website` | `readwrite` | `nodeId` | Create a `static` or `proxy` site (with optional one-shot `ssl_id` HTTPS bind) |
| `enable_website_https` | `readwrite` | `nodeId` | Bind an existing SSL certificate (`ssl_id`) for HTTPS/HSTS/HTTP3, or disable HTTPS |
| `list_website_proxies` | `readonly` | `nodeId` | List reverse-proxy location rules on `website_id` |
| `upsert_website_proxy` | `readwrite` | `nodeId` | Create or edit a reverse-proxy rule (`proxy_pass`, `proxy_host`, SNI, cache, sub-filters) |
| `delete_website_proxy` | `readwrite` | `nodeId` | Delete a named reverse-proxy rule on `website_id` |
| `operate_website` | `readwrite` | `nodeId` | `start` or `stop` a website |
| `delete_website` | `full` | `nodeId` | Delete a website (`deleteApp: false`, `deleteDB: false` safety defaults) |

### SSL Certificates (3)
| Tool | Access | Scope | Description |
| :--- | :---: | :---: | :--- |
| `list_ssls` | `readonly` | `nodeId` | List SSL certificate metadata (private keys & PEM stripped) |
| `get_ssl_metadata` | `readonly` | `nodeId` | Get certificate details by `ssl_id` |
| `create_ssl` | `readwrite` | `nodeId` | Submit ACME DNS-01 or HTTP-01 certificate issuance |

### App Store Updates & Lifecycle (8)
| Tool | Access | Scope | Description |
| :--- | :---: | :---: | :--- |
| `list_installed_apps` | `readonly` | `nodeId` | List installed apps (`canUpdate`, versions, ports; strips `.env` & compose) |
| `get_installed_app` | `readonly` | `nodeId` | Inspect a single installed app by `app_install_id` |
| `list_app_versions` | `readonly` | `nodeId` | List catalog versions for an `app_key` |
| `sync_app_store` | `readwrite` | `nodeId` | Sync remote App Store catalog across Main & Child nodes |
| `get_app_update_versions` | `readonly` | `nodeId` | List available target `detailId` upgrade versions for an installed app |
| `operate_installed_app` | `readwrite` | `nodeId` | `upgrade` (with backup), `restart`, `start`, `stop`, or `rebuild` an app |
| `install_mysql` | `full` | `nodeId` | Install MySQL from the App Store |
| `install_openresty` | `full` | `nodeId` | Install OpenResty from the App Store |

### 1Panel Self-Upgrade & Rollback (5)
| Tool | Access | Scope | Description |
| :--- | :---: | :---: | :--- |
| `check_panel_upgrades` | `readonly` | Cluster | Compare Main & Child 1Panel versions against upstream releases |
| `upgrade_main_panel` | `full` | Main | Upgrade Main Node (`1panel-core` + `1panel-agent`) and poll across restart |
| `upgrade_child_node` | `full` | Child | Upgrade X-Pack Child Node `1panel-agent` (enforces Main-first rule) |
| `get_child_node_upgrade_logs` | `readonly` | Child | List upgrade history & backup availability for a Child Node |
| `rollback_child_node` | `full` | Child | Roll back a Child Node using a verified `upgrade_log_id` |

### Docker Containers (3)
| Tool | Access | Scope | Description |
| :--- | :---: | :---: | :--- |
| `list_containers` | `readonly` | `nodeId` | List App Store + custom Docker containers (`state`, `ports`, `imageName`) |
| `operate_container` | `readwrite` | `nodeId` | `start`, `stop`, or `restart` a container |
| `get_container_logs` | `readonly` | `nodeId` | Fetch bounded tail logs (`<= 500` lines / `64 KiB`, ANSI & secret scrubbed) |

### Databases — MySQL & PostgreSQL (6)
| Tool | Access | Scope | Description |
| :--- | :---: | :---: | :--- |
| `list_mysql_instances` | `readonly` | `nodeId` | List local MySQL/MariaDB instances |
| `list_databases` | `readonly` | `nodeId` | List schemas inside a local MySQL instance |
| `get_mysql_status` | `readonly` | `nodeId` | Read MySQL uptime, connection, thread, and traffic counters |
| `create_database` | `readwrite` | `nodeId` | Create a UTF8MB4 database in a local MySQL instance |
| `list_postgresql_instances` | `readonly` | `nodeId` | List local PostgreSQL instances |
| `list_postgresql_databases` | `readonly` | `nodeId` | List databases inside a local PostgreSQL instance |

---

## Safe Multi-Node Panel Upgrade Workflow

When upgrading 1Panel across a Main + Child cluster (`PANEL_ACCESS_LEVEL=full`):

1. Run `check_panel_upgrades` to inspect current versions on `"local"` and all Child Nodes against upstream releases.
2. Run `upgrade_main_panel` (`target_version: "v2.x.y"`) **first**. When connected via `PANEL_SSH_HOST`, the local MCP server survives `1panel-core` restarting and polls until the Main Node reports healthy on `v2.x.y`.
3. Run `upgrade_child_node` (`nodeId: "3"`) for each Child Node so X-Pack pushes the matching `v2.x.y` `1panel-agent` binary.
4. Use `get_child_node_upgrade_logs` (and `rollback_child_node` if ever needed) to verify completion.

---

## Environment Variables Reference

| Variable | CLI Flag | Default | Description |
| :--- | :--- | :--- | :--- |
| `PANEL_SSH_HOST` | `--ssh-host` | — | SSH config alias or `user@host` for auto-reconnecting loopback tunnel |
| `PANEL_REMOTE_PORT` | `--remote-port` | `20005` | Remote 1Panel port forwarded over SSH |
| `PANEL_SSH_PORT` | `--ssh-port` | — | Optional custom SSH port |
| `PANEL_SSH_KEY_PATH` | `--ssh-key-path` | — | Optional SSH identity key file |
| `PANEL_HOST` | `--host` | — | Direct `http(s)://` 1Panel URL (when not using `PANEL_SSH_HOST`) |
| `PANEL_ACCESS_TOKEN` | `--token` | *(required)* | 1Panel API Key (alias: `PANEL_API_KEY`) |
| `PANEL_KEY_ID` | `--key-id` | — | Optional `1Panel-Key-ID` header |
| `PANEL_BIND_DOMAIN` | `--bind-domain` | — | Optional HTTP `Host` header override when 1Panel Domain Binding is active |
| `PANEL_TLS_INSECURE` | `--tls-insecure` | `false` | Skip TLS certificate verification on self-signed `PANEL_HOST` |
| `PANEL_ACCESS_LEVEL` | `--access-level` | `readwrite` | `readonly` \| `readwrite` \| `full` (`admin`) |
| `MCP_TRANSPORT` | `--transport` | `stdio` | `stdio` \| `http` (`streamable-http`) |
| `MCP_HTTP_ADDR` | `--addr` | `127.0.0.1:8000` | Listen address for Streamable HTTP transport (`/mcp`) |
| `MCP_AUTH_TOKEN` | `--mcp-token` | — | Required Bearer token for Streamable HTTP transport |

---

## License

Licensed under the [Apache License, Version 2.0](./LICENSE). Clean-room TypeScript implementation inspired by and compatible with [1Panel v2](https://github.com/1Panel-dev/1Panel) and [`1Panel-dev/mcp-1panel`](https://github.com/1Panel-dev/mcp-1panel).

TDQS

A3.5/5.0

Scored across 33 tools

Disambiguation5/5

Each tool has a distinct resource-and-action purpose, and even potentially close pairs (system info vs dashboard info, installed apps vs containers, MySQL instances vs databases) are clearly separated by explicit descriptions. No true duplicate or overlapping operations are apparent.

Naming Consistency5/5

Names consistently use snake_case with predictable verb_noun phrasing such as list_websites, get_website, create_ssl, and operate_container. Longer names like get_child_node_upgrade_logs are still readable and follow the same convention.

Tool Count2/5

The server exposes 33 tools, which is heavy for an agent-facing surface and exceeds the well-scoped range. Although the domain is broad, several website, app, and database operations could be consolidated or scoped more tightly to reduce selection cost.

Completeness3/5

The surface covers many reads and some lifecycle operations across websites, SSL, apps, containers, databases, nodes, and upgrades. However, it lacks notable operations such as website/database/SSL deletion, app install/uninstall, and cronjob management, leaving gaps for full 1Panel administration.

Maintenance

ActivityMaintained
ResponsivenessNo recent community issues