mcp-portainer-lite
by Cuti27
README.md
# mcp-portainer-lite
[](https://www.npmjs.com/package/@cuti27/mcp-portainer-lite)
[](https://github.com/cuti27/mcp-portainer-lite/pkgs/container/mcp-portainer-lite)
[](https://github.com/cuti27/mcp-portainer-lite/actions/workflows/docker-publish.yml)
[](LICENSE)
Lightweight [MCP](https://modelcontextprotocol.io) server for [Portainer CE](https://github.com/portainer/portainer), focused on **Docker Swarm** environments. Unofficial project.
## Why this project?
The [official Portainer MCP server](https://github.com/portainer/portainer-mcp) is comprehensive but heavy. `mcp-portainer-lite` is:
- **Lightweight**: no unnecessary dependencies, built with Node.js HTTP, Zod, and Pino.
- **Swarm-focused**: tools designed for Swarm environments.
- **Write-protected**: mutation operations are disabled by default.
- **Dual transport**: works over stdio (npx) and HTTP (Docker).
### Comparison with portainer/portainer-mcp
| Aspect | portainer/portainer-mcp | mcp-portainer-lite |
|---|---|---|
| Size | ~25 MB installed | ~8 MB installed |
| Focus | Docker + Swarm | Swarm only |
| Mutations | Always enabled | `ALLOW_MUTATIONS=true` required |
| Transports | stdio | stdio + HTTP |
| Auth | API key or credentials | API key + optional bearer token |
## Requirements
- Node.js >= 20 (for npx usage) or Docker (for container)
- Portainer CE 2.39.x or higher
- Portainer API key with read permissions (and write if using mutations)
## Installation
### Docker Compose (recommended)
```yaml
version: '3.8'
services:
portainer-mcp:
image: ghcr.io/cuti27/mcp-portainer-lite:latest
container_name: portainer-mcp
ports:
- "3000:3000"
environment:
- PORTAINER_URL=http://portainer:9000
- PORTAINER_API_KEY=ptr_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- PORTAINER_ENDPOINT_ID=1
- ALLOW_MUTATIONS=false
- MCP_AUTH_TOKEN=your-secure-token-here
- PORT=3000
- HOST=0.0.0.0
# Only for plain HTTP Portainer. Use HTTPS in production.
- PORTAINER_ALLOW_HTTP=true
networks:
- portainer-net
networks:
portainer-net:
external: true
```
> ⚠️ **Security warning**: `HOST=0.0.0.0` exposes the MCP server to the network. Only use it inside Docker with `MCP_AUTH_TOKEN` set, and put the container behind a TLS-terminating reverse proxy. Never expose the HTTP transport to the internet without authentication and TLS.
### npx
```bash
PORTAINER_URL=http://localhost:9000 \
PORTAINER_API_KEY=ptr_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
PORTAINER_ENDPOINT_ID=1 \
PORTAINER_ALLOW_HTTP=true \
npx -y @cuti27/mcp-portainer-lite
```
### Claude Desktop
Add to your `claude_desktop_config.json`:
```json
{
"mcpServers": {
"portainer": {
"command": "npx",
"args": ["-y", "@cuti27/mcp-portainer-lite"],
"env": {
"PORTAINER_URL": "http://localhost:9000",
"PORTAINER_API_KEY": "ptr_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"PORTAINER_ENDPOINT_ID": "1",
"PORTAINER_ALLOW_HTTP": "true"
}
}
}
}
```
## Environment variables
| Variable | Required | Default | Description |
|---|---|---|---|
| `PORTAINER_URL` | Yes | — | Portainer base URL (e.g. `http://portainer:9000`) |
| `PORTAINER_API_KEY` | Yes | — | Portainer API key |
| `PORTAINER_ENDPOINT_ID` | No | first endpoint | Default endpoint ID |
| `ALLOW_MUTATIONS` | No | `false` | Enable write tools |
| `MCP_TRANSPORT` | No | `stdio` | Transport: `stdio` or `http` |
| `MCP_AUTH_TOKEN` | Only for HTTP | — | Bearer token to authenticate MCP HTTP requests |
| `PORT` | No | `3000` | HTTP port (http transport only) |
| `HOST` | No | `127.0.0.1` | HTTP bind address (http transport only) |
| `PORTAINER_ALLOW_HTTP` | No | `false` | Allow plain HTTP for `PORTAINER_URL` (not recommended for production) |
| `TLS_CERT_PATH` | No | — | Path to TLS certificate (HTTPS transport directly) |
| `TLS_KEY_PATH` | No | — | Path to TLS private key (HTTPS transport directly) |
| `TRUSTED_PROXIES` | No | — | Comma-separated trusted proxy IPs/CIDRs for `X-Forwarded-For` parsing |
| `LOG_LEVEL` | No | `info` | Log level: trace/debug/info/warn/error/fatal |
## Available tools
### Phase 1 — Read (always available)
| Tool | Description |
|---|---|
| `list_endpoints` | List Portainer-managed environments |
| `list_stacks` | List stacks for an endpoint |
| `get_stack` | Get stack details |
| `get_service` | Get service details |
| `list_services` | List Swarm services for an endpoint |
| `get_service_logs` | Get last N log lines from a service (best-effort sensitive data redaction) |
| `get_endpoint_summary` | Get endpoint summary |
### Phase 2 — Mutation (only with `ALLOW_MUTATIONS=true`)
| Tool | Description |
|---|---|
| `restart_service` | Restart a service |
| `start_service` | Scale a service to 1 replica |
| `stop_service` | Scale a service to 0 replicas |
| `restart_stack` | Restart all services in a stack |
## Security
- **Authentication**: When using HTTP transport, `MCP_AUTH_TOKEN` is required. All requests must include `Authorization: Bearer <token>`. Requests without a valid token receive `401 Unauthorized`.
- **API key redaction**: The Portainer API key and Authorization header are automatically redacted from logs by Pino.
- **Write protection**: Mutation operations are **disabled by default**. Enable them only with `ALLOW_MUTATIONS=true`.
- **Input validation**: All service IDs, endpoint IDs, and stack IDs are validated against alphanumeric patterns before being used in API calls, preventing path traversal.
- **Rate limiting**: The HTTP server enforces 60 requests per minute per IP. Excess requests receive `429 Too Many Requests`.
- **Body size limit**: HTTP POST bodies are limited to 1 MB. Larger payloads receive `413 Payload Too Large`.
- **Safe error messages**: Internal error details are logged server-side but never returned to the client.
- **Host binding**: Defaults to `127.0.0.1` (localhost only). If set to `0.0.0.0`, a warning is emitted.
- **HTTPS recommended**: Use `https://` for `PORTAINER_URL` in production. A startup warning is shown if using plain HTTP.
- **Principle of least privilege**: Use a Portainer API key with **minimum required permissions** (read-only if mutations are not needed).
## Compatibility
Tested with **Portainer CE 2.39.x**. Compatible with newer versions barring breaking API changes.
## Local test environment
The repository includes helper scripts to spin up a disposable Docker Swarm sandbox with Portainer CE and dummy services for safe end-to-end testing.
```bash
# Start the test environment (Portainer CE on http://localhost:9000)
./scripts/test-swarm-up.sh
# The script prints credentials and writes a ready-to-use .env.test file.
source .env.test
npm run start:stdio
# Destroy everything when done
./scripts/test-swarm-down.sh
```
These scripts only touch resources named `portainer-test` or `test-apps`; your existing Docker containers, networks, and volumes are left untouched.
## Development
```bash
# Clone
git clone https://github.com/cuti27/mcp-portainer-lite.git
cd mcp-portainer-lite
# Install
npm install
# Watch mode
npm run dev
# Build
npm run build
# Run
npm start
```
## Tests
```bash
# Unit + integration tests
npm test
# With coverage
npm run test:coverage
```
## License
MIT. See [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues