portainer-mcp
Enables AI assistants to manage Portainer container environments through natural language, including deploying stacks, managing containers, images, volumes, networks, and executing commands.
README
<p align="center"> <img src="https://minio.ginkida.dev/minion/github/portainer-mcp.png" alt="Portainer MCP Server" width="600"> </p>
Portainer MCP Server
An MCP (Model Context Protocol) server that gives AI assistants — Claude, Copilot, Cursor, and others — 41 tools to manage Portainer container environments: deploy and update stacks, manage containers/images/volumes/networks, exec commands, analyze logs, debug Laravel apps, and inspect endpoints — all through natural language.
For LLM agents: This server connects via stdio transport. Every tool returns JSON. All mutating operations are audit-logged. Credentials are passed via environment variables, never hardcoded.
Why Use This
- Natural language DevOps — Ask your AI assistant to deploy a stack, check container logs, or pull an image.
- Swarm-aware — Automatically detects Docker Swarm clusters and uses the correct API.
- Safe by default — Input validation, path traversal protection, sensitive field filtering, and force-remove disabled by default.
- Works everywhere — Claude Desktop, Claude Code, Cursor, Windsurf, VS Code, Continue.dev.
Quick Start
1. Install
pip install portainer-mcp
Or from source:
git clone https://github.com/ginkida/portainer-mcp.git
cd portainer-mcp
pip install -e .
2. Configure your AI client
Pick your client below, paste the config, and replace the placeholder values with your Portainer credentials.
Client Configuration
Claude Desktop
File: ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows)
{
"mcpServers": {
"portainer": {
"command": "python3",
"args": ["-m", "portainer_mcp.server"],
"env": {
"PORTAINER_URL": "https://your-portainer:9443",
"PORTAINER_USERNAME": "admin",
"PORTAINER_PASSWORD": "your-password",
"PORTAINER_VERIFY_SSL": "false"
}
}
}
}
Claude Code
File: .mcp.json in your project root (project-scope) or ~/.claude.json (user-scope)
{
"mcpServers": {
"portainer": {
"type": "stdio",
"command": "python3",
"args": ["-m", "portainer_mcp.server"],
"env": {
"PORTAINER_URL": "https://your-portainer:9443",
"PORTAINER_USERNAME": "admin",
"PORTAINER_PASSWORD": "${PORTAINER_PASSWORD}",
"PORTAINER_VERIFY_SSL": "false"
}
}
}
}
Or via CLI:
claude mcp add portainer -- python3 -m portainer_mcp.server
Cursor
File: ~/.cursor/mcp.json (global) or .cursor/mcp.json (project)
{
"mcpServers": {
"portainer": {
"command": "python3",
"args": ["-m", "portainer_mcp.server"],
"env": {
"PORTAINER_URL": "https://your-portainer:9443",
"PORTAINER_USERNAME": "admin",
"PORTAINER_PASSWORD": "your-password",
"PORTAINER_VERIFY_SSL": "false"
}
}
}
}
Windsurf
File: ~/.codeium/windsurf/mcp_config.json
{
"mcpServers": {
"portainer": {
"command": "python3",
"args": ["-m", "portainer_mcp.server"],
"env": {
"PORTAINER_URL": "https://your-portainer:9443",
"PORTAINER_USERNAME": "admin",
"PORTAINER_PASSWORD": "your-password",
"PORTAINER_VERIFY_SSL": "false"
}
}
}
}
VS Code (GitHub Copilot)
File: .vscode/mcp.json in your workspace
{
"servers": {
"portainer": {
"type": "stdio",
"command": "python3",
"args": ["-m", "portainer_mcp.server"],
"env": {
"PORTAINER_URL": "${input:portainer-url}",
"PORTAINER_USERNAME": "${input:portainer-username}",
"PORTAINER_PASSWORD": "${input:portainer-password}",
"PORTAINER_VERIFY_SSL": "false"
}
}
},
"inputs": [
{ "type": "promptString", "id": "portainer-url", "description": "Portainer base URL" },
{ "type": "promptString", "id": "portainer-username", "description": "Portainer username" },
{ "type": "promptString", "id": "portainer-password", "description": "Portainer password", "password": true }
]
}
Continue.dev
File: ~/.continue/config.yaml or .continue/config.yaml
mcpServers:
- name: portainer
type: stdio
command: python3
args:
- -m
- portainer_mcp.server
env:
PORTAINER_URL: "https://your-portainer:9443"
PORTAINER_USERNAME: "admin"
PORTAINER_PASSWORD: "your-password"
PORTAINER_VERIFY_SSL: "false"
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
PORTAINER_URL |
Yes | — | Portainer base URL (e.g. https://portainer.example.com:9443) |
PORTAINER_USERNAME |
Yes | — | Portainer username |
PORTAINER_PASSWORD |
Yes | — | Portainer password |
PORTAINER_DEFAULT_ENDPOINT |
No | 1 |
Default endpoint ID for container/image/stack operations |
PORTAINER_VERIFY_SSL |
No | true |
Set to false for self-signed certificates |
Tools
All 41 tools are listed below with their parameters and descriptions. Every tool returns JSON.
Authentication
| Tool | Description |
|---|---|
portainer_status() |
Check connection and authentication status. Returns version and instance ID. |
Endpoints (Environments)
| Tool | Description |
|---|---|
portainer_endpoints_list() |
List all environments. Returns id, name, type, url, status. |
portainer_endpoint_inspect(endpoint_id) |
Get endpoint details (sensitive fields like TLS certs are filtered). |
Stacks
| Tool | Description |
|---|---|
portainer_stacks_list() |
List all stacks with id, name, type, status, endpoint_id. |
portainer_stack_inspect(stack_id) |
Get stack details including the docker-compose file content. |
portainer_stack_deploy(name, compose_content, endpoint_id?) |
Deploy a new stack. Auto-detects Swarm vs standalone. |
portainer_stack_update(stack_id, compose_content?, endpoint_id?) |
Update a stack. Omit compose_content to redeploy existing. |
portainer_stack_delete(stack_id) |
Delete a stack. |
portainer_stack_start(stack_id) |
Start a stopped stack. |
portainer_stack_stop(stack_id) |
Stop a running stack. |
Containers
| Tool | Description |
|---|---|
portainer_containers_list(endpoint_id?, show_all?) |
List containers. Set show_all=true to include stopped. |
portainer_container_inspect(container_id, endpoint_id?) |
Get detailed container info. |
portainer_container_start(container_id, endpoint_id?) |
Start a stopped container. |
portainer_container_stop(container_id, endpoint_id?) |
Stop a running container. |
portainer_container_restart(container_id, endpoint_id?) |
Restart a container. |
portainer_container_remove(container_id, force?, endpoint_id?) |
Remove a container. force defaults to false. |
portainer_container_logs(container_id, tail?, endpoint_id?) |
Get container logs. tail defaults to 100 (max 1000). |
portainer_container_logs_grep(container_id, pattern, tail?, context_lines?, endpoint_id?) |
Server-side regex over logs. Returns only matching lines (with optional context) — saves bandwidth on noisy logs. |
portainer_container_stats(container_id, endpoint_id?) |
Point-in-time CPU%, memory, network and block I/O stats (not a stream). |
portainer_container_exec(container_id, command, workdir?, user?, endpoint_id?) |
Run a shell command inside a running container and return its stdout/stderr + exit code. Audit-logged. |
portainer_stack_logs_errors(stack_name, tail?, endpoint_id?) |
Concurrent scan of every running container in a stack for HTTP 4xx/5xx, exceptions, fatal/critical levels, panics, OOM, PHP errors, etc. |
portainer_laravel_errors(stack_name, tail?, endpoint_id?) |
Read /var/www/app/storage/logs/laravel.log inside each container of a stack and return production.ERROR/CRITICAL/EMERGENCY entries — the actual exception behind a 500. |
portainer_laravel_tinker(stack_name, code, endpoint_id?) |
Execute PHP via php artisan tinker --execute=... in the first running {stack}_backend.* container. Code capped at 4096 chars. Audit-logged. |
Images
| Tool | Description |
|---|---|
portainer_images_list(endpoint_id?) |
List images with tags and sizes. |
portainer_image_inspect(image_id, endpoint_id?) |
Get detailed image info. Accepts name:tag or name@sha256:digest. |
portainer_image_pull(image_name, tag?, registry_auth?, endpoint_id?) |
Pull an image. tag defaults to "latest". registry_auth is an optional base64-encoded JSON {"username":..,"password":..,"serveraddress":..} forwarded as X-Registry-Auth — required for private registries. The pull-progress stream is parsed and any errorDetail is surfaced as a tool error (the previous version returned success regardless). |
portainer_image_remove(image_id, endpoint_id?) |
Remove an image. |
Volumes
| Tool | Description |
|---|---|
portainer_volumes_list(endpoint_id?) |
List Docker volumes. |
portainer_volume_inspect(volume_name, endpoint_id?) |
Get detailed volume info. |
portainer_volume_create(name, driver?, labels?, endpoint_id?) |
Create a volume. driver defaults to "local". |
portainer_volume_remove(volume_name, force?, endpoint_id?) |
Remove a volume. force defaults to false. |
Networks
| Tool | Description |
|---|---|
portainer_networks_list(endpoint_id?) |
List Docker networks with driver, scope and attached container count. |
portainer_network_inspect(network_id, endpoint_id?) |
Get detailed network info. |
portainer_network_create(name, driver?, internal?, labels?, endpoint_id?) |
Create a network. driver defaults to "bridge" (use "overlay" for Swarm). |
portainer_network_remove(network_id, endpoint_id?) |
Remove a network. |
portainer_network_connect(network_id, container_id, endpoint_id?) |
Attach a container to a network. |
portainer_network_disconnect(network_id, container_id, force?, endpoint_id?) |
Detach a container from a network. |
System
| Tool | Description |
|---|---|
portainer_docker_info(endpoint_id?) |
OS, CPU, memory, container/image counts, swarm state. |
portainer_docker_disk_usage(endpoint_id?) |
Per-category disk usage (containers, images, volumes, build cache) with reclaimable size. |
Users
| Tool | Description |
|---|---|
portainer_users_list() |
List all Portainer users with id, username, role. |
portainer_user_inspect(user_id) |
Get user details. Sensitive fields (password hash, TFA material, tokens) are filtered out. |
Example Workflows
Deploy a new service:
"Deploy a stack called 'redis' with Redis 7 on port 6379"
The agent will call portainer_stack_deploy(name="redis", compose_content="...") with the generated compose YAML.
Debug a failing container:
"Why is the nginx container crashing?"
The agent will call portainer_containers_list() to find the container, then portainer_container_logs(container_id) to inspect the logs.
Update an existing stack:
"Update the arena-etl stack to use the new image tag v2.1"
The agent will call portainer_stack_inspect(stack_id) to get the current compose file, modify the image tag, then portainer_stack_update(stack_id, compose_content).
Security
- JWT auth with proactive refresh (7h TTL, Portainer default is 8h),
asyncio.Lock-guarded re-authentication for safe concurrent use, and 401/403-CSRF retry fallback. - CSRF handling for Portainer 2.39+ — Referer +
X-CSRF-Tokenare sent only on mutating methods; CSRF token is harvested fromX-CSRF-Tokenresponse headers and refreshed automatically. - SSL verification enabled by default. Only disable for self-signed certificates.
- Input validation — container IDs, image references (incl. digests), stack names, volume/network names are regex-validated before any API call. Path traversal (
..) is blocked. - Sensitive field filtering —
endpoint_inspectstrips TLS certificates, Azure credentials and security settings;user_inspectwhitelists safe fields and hides password/TFA material. - Audit logging — every mutating operation (deploy, delete, remove, pull, start, stop, exec, tinker) is logged to stderr with parameters.
- No hardcoded credentials — all secrets come from environment variables. Optional
X-Registry-Authfor private-registry image pulls is passed in via parameter, never persisted. - Container removal —
forcedefaults tofalseto prevent accidental deletion of running containers. - Log/exec size limits — output is capped at 100K characters to prevent memory exhaustion.
Development
git clone https://github.com/ginkida/portainer-mcp.git
cd portainer-mcp
pip install -e ".[dev]"
Run locally:
export PORTAINER_URL=https://your-portainer:9443
export PORTAINER_USERNAME=admin
export PORTAINER_PASSWORD=your-password
python3 -m portainer_mcp.server
Lint and type-check:
ruff check src/
mypy src/
Requirements
- Python 3.10+
- A running Portainer instance (CE or Business Edition)
- Portainer API access (default port 9443)
License
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。