Ever wished you could just ask Portainer what's going on?
Now you can! Portainer MCP connects your AI assistant directly to your Portainer environments. Manage Portainer resources such as users and environments, or dive deeper by executing any Docker or Kubernetes command directly through the AI.
Portainer MCP Safe is an altered-source fork of Portainer MCP by Portainer.io. Original upstream source: https://github.com/portainer/portainer-mcp Fork repository target: https://github.com/Malaccamaxgit/portainer-mcp-safe
This fork keeps the upstream Portainer MCP tool surface and implementation model, then adds AI-safety controls for stack output redaction and proxy restrictions.
Portainer MCP is a work in progress implementation of the Model Context Protocol (MCP) for Portainer environments. The upstream project aims to provide a standardized way to connect Portainer's container management capabilities with AI models and other services.
MCP (Model Context Protocol) is an open protocol that standardizes how applications provide context to LLMs (Large Language Models). Similar to how USB-C provides a standardized way to connect devices to peripherals, MCP provides a standardized way to connect AI models to different data sources and tools.
This implementation focuses on exposing Portainer environment data through the MCP protocol, allowing AI assistants and other tools to interact with your containerized infrastructure in a secure and standardized way.
Note
This tool is designed to work with specific Portainer versions. If your Portainer version doesn't match the supported version, you can use the --disable-version-check flag to attempt connection anyway. See Portainer Version Support for compatible versions and Disable Version Check for bypass instructions.
See the Supported Capabilities sections for more details on compatibility and available features.
Note: This project is currently under development.
It is currently designed to work with a Portainer administrator API token.
This repository is maintained by Benjamin Alloul. It is a reworked fork of the upstream Portainer MCP project, adapted to:
- redact sensitive fields in stack output
- work within Docker MCP Toolkit through the Docker MCP Gateway
The original upstream source is: https://github.com/portainer/portainer-mcp
This fork keeps the upstream Portainer MCP tool surface and implementation model, then adds AI-safety controls for stack output redaction and proxy restrictions.
- safe-mode redaction for secret-like stack environment values and compose content
- Docker proxy allowlisting by default
- Kubernetes secret-path blocking and secret-like JSON field redaction by default
- edition-aware tool registration for Portainer Community Edition vs Business Edition
Repository layout:
- Go fork source lives at the repo root
- Docker MCP Toolkit packaging lives in
docker/
The upstream license is retained in LICENSE, and fork attribution is captured
in NOTICE.
This fork is intentionally marked as altered source to respect the upstream license terms:
- it does not claim to be the original Portainer MCP project
- it retains the upstream
LICENSE - it names the upstream project and source URL prominently
- it documents fork-specific behavior separately from upstream behavior
A lightweight GitHub Pages site is prepared from docs/ and is intended to
publish at:
https://malaccamaxgit.github.io/portainer-mcp-safe/
This fork uses a rebase-on-tag workflow instead of a long-running patch pile.
- Add the upstream remote once:
git remote add upstream https://github.com/portainer/portainer-mcp.git - Fetch upstream tags:
git fetch upstream --tags - Create a temporary sync branch from the next upstream release tag:
git checkout -b sync/vX.Y.Z upstream/vX.Y.Z - Rebase the fork branch onto that tag and resolve conflicts in this order:
internal/tooldef/tools.yaml,internal/mcp/*,internal/safety/*,cmd/portainer-mcp/mcp.go - Re-run the fork unit tests and regenerate the wrapper metadata before publishing.
The authoritative tool definitions for this repo are:
internal/tooldef/tools.yamlfor the forked binary- the embedded
tools:block indocker/portainer-mcp-gateway.yamlfor Docker MCP Toolkit
After changing internal/tooldef/tools.yaml, run:
make regen-gateway-tools
If make is not available in your shell, run:
go run ./cmd/regen-gateway-tools
The regen tool also supports two read-only modes that are useful in CI and during reviews:
go run ./cmd/regen-gateway-tools --checkexits non-zero ifdocker/portainer-mcp-gateway.yamlwould be modified by a regeneration.go run ./cmd/regen-gateway-tools --check --diffdoes the same and prints a line-level diff of the drift to stderr so you can see exactly what would change without writing anything.
The tools: block in docker/portainer-mcp-gateway.yaml is generated and is
prefixed with an # AUTO-GENERATED marker. Do not hand-edit it: change
internal/tooldef/tools.yaml and rerun the regen tool.
Any file written under docker/generated/ is a local regeneration scratch artifact
used to help refresh the gateway YAML. It should stay ignored and should not be
treated as a committed source of truth.
If an upstream release substantially rewrites proxy or stack handlers, prefer a fresh re-application of the safety layer over carrying forward a conflict-heavy rebase mechanically.
This repo is currently set up to be built locally or packaged into your own registry/catalog flow. It does not assume an official public release channel for the fork.
Build the binary locally:
go build -o dist/portainer-mcp ./cmd/portainer-mcpBuild the containerized unit-test image and run the tests:
docker build -f docker/Dockerfile --target test -t portainer-mcp-safe-test .
docker run --rm portainer-mcp-safe-testThe test stage's default CMD runs go test ./cmd/... ./internal/... ./pkg/...,
which includes the gateway metadata sync test. Override the CMD on docker run
to scope to a narrower target.
Build the lean Docker Toolkit runtime image from the unified repo root:
docker build -f docker/Dockerfile -t portainer-mcp-safe:0.7.0-safe.1 .The test target intentionally runs unit checks only. Integration tests stay
outside the image build because they require a Docker-capable test runner rather
than a nested Docker setup inside the build stage.
Docker MCP Toolkit-specific usage lives in docker/USAGE.txt.
-server <url> Required. Portainer server URL.
-token <token> Required. Portainer API token.
-tools <path> Optional. Path to tools.yaml (default: tools.yaml).
-read-only Optional. Register only read-only tools.
-business-edition Optional. Register Business Edition-only tools.
-disable-version-check Optional. Skip Portainer version validation.
-safe-mode=true Optional. Enable redaction and proxy safety guards.
-allow-unredacted-stack-content=false Optional. Allow raw stack env values and compose content in safe mode.
-allow-sensitive-proxy-paths=false Optional. Allow sensitive Docker and Kubernetes proxy paths in safe mode.
-proxy-allowlist <entries> Optional. Extra METHOD:/path-prefix entries, comma-separated.
-extra-redaction-patterns <patterns> Optional. Extra redaction regex patterns, comma-separated.
With Claude Desktop, configure it like so:
{
"mcpServers": {
"portainer": {
"command": "/path/to/portainer-mcp",
"args": [
"-server",
"[IP]:[PORT]",
"-token",
"[TOKEN]",
"-tools",
"/tmp/tools.yaml"
]
}
}
}
Replace [IP], [PORT] and [TOKEN] with the IP, port and API access token associated with your Portainer instance.
Note
By default, the tool looks for "tools.yaml" in the same directory as the binary. If the file does not exist, it will be created there with the default tool definitions. You may need to modify this path as described above, particularly when using AI assistants like Claude that have restricted write permissions to the working directory.
By default, the application validates that your Portainer server version matches the supported version and will fail to start if there's a mismatch. If you have a Portainer server version that doesn't have a corresponding Portainer MCP version available, you can disable this version check to attempt connection anyway.
To disable the version check, add the -disable-version-check flag to your command arguments:
{
"mcpServers": {
"portainer": {
"command": "/path/to/portainer-mcp",
"args": [
"-server",
"[IP]:[PORT]",
"-token",
"[TOKEN]",
"-disable-version-check"
]
}
}
}
Warning
Disabling the version check may result in unexpected behavior or API incompatibilities if your Portainer server version differs significantly from the supported version. The tool may work partially or not at all with unsupported versions.
When using this flag:
- The application will skip Portainer server version validation at startup
- Some features may not work correctly due to API differences between versions
- Newer Portainer versions may have API changes that cause errors
- Older Portainer versions may be missing APIs that the tool expects
This flag is useful when:
- You're running a newer Portainer version that doesn't have MCP support yet
- You're running an older Portainer version and want to try the tool anyway
By default, the tool definitions are embedded in the binary. The application will create a tools file at the default location if one doesn't already exist.
You can customize the tool definitions by specifying a custom tools file path using the -tools flag:
{
"mcpServers": {
"portainer": {
"command": "/path/to/portainer-mcp",
"args": [
"-server",
"[IP]:[PORT]",
"-token",
"[TOKEN]",
"-tools",
"/path/to/custom/tools.yaml"
]
}
}
}
The default tools file is available for reference at internal/tooldef/tools.yaml in the source code. You can modify the descriptions of the tools and their parameters to alter how AI models interpret and decide to use them. You can even decide to remove some tools if you don't wish to use them.
Warning
Do not change the tool names or parameter definitions (other than descriptions), as this will prevent the tools from being properly registered and functioning correctly.
For security-conscious users, the application can be run in read-only mode. This mode ensures that only read operations are available, completely preventing any modifications to your Portainer resources.
To enable read-only mode, add the -read-only flag to your command arguments:
{
"mcpServers": {
"portainer": {
"command": "/path/to/portainer-mcp",
"args": [
"-server",
"[IP]:[PORT]",
"-token",
"[TOKEN]",
"-read-only"
]
}
}
}
When using read-only mode:
- Only read-only registrations are loaded (no create, update, or delete tools)
- All write tools (create, update, delete) are not loaded
- The Docker and Kubernetes proxy tools are available but restricted to GET requests only
By default, the server starts in Community Edition mode. In that mode it hides the tools that depend on Portainer Business Edition APIs so MCP clients do not see tools that would fail at runtime on CE.
To enable the Business Edition tool set, add the -business-edition flag to
your command arguments:
{
"mcpServers": {
"portainer": {
"command": "/path/to/portainer-mcp",
"args": [
"-server",
"[IP]:[PORT]",
"-token",
"[TOKEN]",
"-business-edition"
]
}
}
}
When -business-edition is not set:
- Edge Stack tools are hidden
- Environment Group tools are hidden
- Access Group tools are hidden
- Environment user/team access policy tools are hidden
When -business-edition is set:
- The full 39-tool Portainer surface is registered
This flag only affects MCP tool registration. The Docker MCP Toolkit catalog can still advertise the full static tool list, but the running server exposes the correct subset to the MCP client.
The updateUserRole tool remains available in both modes. On Community
Edition, Portainer may still reject the edge_admin role at call time.
Safe mode is enabled by default in this fork. It redacts secret-like values in stack content and applies safety guards to Docker and Kubernetes proxy access.
| Flag | Default | Purpose |
|---|---|---|
-safe-mode |
true |
Enable redaction and proxy safety guards |
-allow-unredacted-stack-content |
false |
Allow raw stack env values and compose content in safe mode |
-allow-sensitive-proxy-paths |
false |
Allow sensitive Docker and Kubernetes proxy paths in safe mode |
-proxy-allowlist |
"" |
Add METHOD:/path-prefix allowlist entries, comma-separated |
-extra-redaction-patterns |
"" |
Add extra regex patterns for secret-like key names |
When safe mode is enabled:
- Stack and local stack output can be redacted when environment values look secret-like
- Docker proxy requests are restricted to safe paths unless explicitly allowed
- Kubernetes proxy requests block sensitive secret paths unless explicitly allowed
- Kubernetes JSON responses can have secret-like fields redacted
Use the override flags sparingly. They are intended for trusted local workflows where the default safety layer is too restrictive.
This tool is pinned to support a specific version of Portainer. The application will validate the Portainer server version at startup and fail if it doesn't match the required version.
| Portainer MCP Version | Supported Portainer Version |
|---|---|
| 0.1.0 | 2.28.1 |
| 0.2.0 | 2.28.1 |
| 0.3.0 | 2.28.1 |
| 0.4.0 | 2.29.2 |
| 0.4.1 | 2.29.2 |
| 0.5.0 | 2.30.0 |
| 0.6.0 | 2.31.2 |
| 0.7.0 | 2.31.2 |
Note
If you need to connect to an unsupported Portainer version, you can use the -disable-version-check flag to bypass version validation. See the Disable Version Check section for more details and important warnings about using this feature.
The following table lists the currently (latest version) supported operations through MCP tools:
Note
Edge Stacks vs Local Stacks: The original Portainer MCP only supports Edge Stacks (distributed via Edge Groups). Local Stack tools add support for regular standalone Docker Compose stacks deployed directly on environments — the most common stack type in non-Edge setups. Local Stack tools use raw HTTP requests to the Portainer REST API since the official SDK (client-api-go) does not expose regular stack endpoints.
Note
Community vs Business Edition: internal/tooldef/tools.yaml still defines the full tool surface, but the server now hides the 18 Business Edition-only tools by default unless -business-edition is enabled.
| Resource | Operation | Description | Edition | Supported In Version |
|---|---|---|---|---|
| Environments | ||||
| ListEnvironments | List all available environments | CE | 0.1.0 | |
| UpdateEnvironmentTags | Update tags associated with an environment | CE | 0.1.0 | |
| UpdateEnvironmentUserAccesses | Update user access policies for an environment | EE | 0.1.0 | |
| UpdateEnvironmentTeamAccesses | Update team access policies for an environment | EE | 0.1.0 | |
| Environment Groups (Edge Groups) | ||||
| ListEnvironmentGroups | List all available environment groups | EE | 0.1.0 | |
| CreateEnvironmentGroup | Create a new environment group | EE | 0.1.0 | |
| UpdateEnvironmentGroupName | Update the name of an environment group | EE | 0.1.0 | |
| UpdateEnvironmentGroupEnvironments | Update environments associated with a group | EE | 0.1.0 | |
| UpdateEnvironmentGroupTags | Update tags associated with a group | EE | 0.1.0 | |
| Access Groups (Endpoint Groups) | ||||
| ListAccessGroups | List all available access groups | EE | 0.1.0 | |
| CreateAccessGroup | Create a new access group | EE | 0.1.0 | |
| UpdateAccessGroupName | Update the name of an access group | EE | 0.1.0 | |
| UpdateAccessGroupUserAccesses | Update user accesses for an access group | EE | 0.1.0 | |
| UpdateAccessGroupTeamAccesses | Update team accesses for an access group | EE | 0.1.0 | |
| AddEnvironmentToAccessGroup | Add an environment to an access group | EE | 0.1.0 | |
| RemoveEnvironmentFromAccessGroup | Remove an environment from an access group | EE | 0.1.0 | |
| Stacks (Edge Stacks) | ||||
| ListStacks | List all available stacks | EE | 0.1.0 | |
| GetStackFile | Get the compose file for a specific stack | EE | 0.1.0 | |
| CreateStack | Create a new Docker stack | EE | 0.1.0 | |
| UpdateStack | Update an existing Docker stack | EE | 0.1.0 | |
| Tags | ||||
| ListEnvironmentTags | List all available environment tags | CE | 0.1.0 | |
| CreateEnvironmentTag | Create a new environment tag | CE | 0.1.0 | |
| Teams | ||||
| ListTeams | List all available teams | CE | 0.1.0 | |
| CreateTeam | Create a new team | CE | 0.1.0 | |
| UpdateTeamName | Update the name of a team | CE | 0.1.0 | |
| UpdateTeamMembers | Update the members of a team | CE | 0.1.0 | |
| Users | ||||
| ListUsers | List all available users | CE | 0.1.0 | |
| UpdateUser | Update an existing user | CE | 0.1.0 | |
| GetSettings | Get the settings of the Portainer instance | CE | 0.1.0 | |
| Docker | ||||
| DockerProxy | Proxy ANY Docker API requests (GET only in read-only mode) | CE | 0.2.0 | |
| Kubernetes | ||||
| KubernetesProxy | Proxy ANY Kubernetes API requests (GET only in read-only mode) | CE | 0.3.0 | |
| getKubernetesResourceStripped | Proxy GET Kubernetes API requests and automatically strip verbose metadata fields | CE | 0.6.0 | |
| Local Stacks (Standalone Docker Compose) | ||||
| ListLocalStacks | List all local (non-edge) stacks deployed on environments | CE | 0.7.0 | |
| GetLocalStackFile | Get the docker-compose file content for a local stack | CE | 0.7.0 | |
| CreateLocalStack | Create a new local standalone Docker Compose stack | CE | 0.7.0 | |
| UpdateLocalStack | Update an existing local stack with new compose file | CE | 0.7.0 | |
| StartLocalStack | Start a stopped local stack | CE | 0.7.0 | |
| StopLocalStack | Stop a running local stack | CE | 0.7.0 | |
| DeleteLocalStack | Delete a local stack permanently | CE | 0.7.0 |
The repository includes a helper script cloc.sh to calculate lines of code and other metrics for the Go source files using the cloc tool. You might need to install cloc first (e.g., sudo apt install cloc or brew install cloc).
Run the script from the repository root to see the default summary output:
./cloc.shRefer to the comment header within the cloc.sh script for details on available flags to retrieve specific metrics.
To get an estimate of how many tokens your current tool definitions consume in prompts, you can use the provided Go program and shell script to query the Anthropic API's token counting endpoint.
1. Generate the Tools JSON:
First, use the token-count Go program to convert your YAML tool definitions into the JSON format required by the Anthropic API. Run this from the repository root:
# Replace internal/tooldef/tools.yaml with your YAML file if different
# Replace .tmp/tools.json with your desired output path
go run ./cmd/token-count -input internal/tooldef/tools.yaml -output .tmp/tools.jsonThis command reads the tool definitions from the specified input YAML file and writes a JSON array of tools (containing name, description, and input_schema) to the specified output file.
2. Query the Anthropic API:
Next, use the token.sh script to send these tool definitions along with a sample message to the Anthropic API. You will need an Anthropic API key for this step.
# Ensure you have jq installed
# Replace sk-ant-xxxxxxxx with your actual Anthropic API key
# Replace .tmp/tools.json with the path to the file generated in step 1
./token.sh -k sk-ant-xxxxxxxx -i .tmp/tools.jsonThe script will output the JSON response from the Anthropic API, which includes the estimated token count for the provided tools and sample message under the usage.input_tokens field.
This process helps in understanding the token cost associated with the toolset provided to the language model.
