helm-mcp
by SCGIS-Wales
README.md
<p align="center">
<img src="assets/helm-mcp-logo.svg" width="500" alt="Helm MCP">
</p>
<p align="center">
<a href="https://github.com/SCGIS-Wales/helm-mcp/actions/workflows/ci.yml"><img src="https://github.com/SCGIS-Wales/helm-mcp/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI/CD Pipeline"></a>
<a href="https://goreportcard.com/report/github.com/SCGIS-Wales/helm-mcp"><img src="https://img.shields.io/badge/go%20report-A+-brightgreen.svg" alt="Go Report Card"></a>
<a href="https://pypi.org/project/helm-mcp/"><img src="https://img.shields.io/pypi/v/helm-mcp" alt="PyPI version"></a>
<a href="https://pypi.org/project/helm-mcp/"><img src="https://img.shields.io/pypi/pyversions/helm-mcp" alt="Python versions"></a>
<a href="https://pypistats.org/packages/helm-mcp"><img src="https://img.shields.io/pypi/dm/helm-mcp" alt="Downloads"></a>
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
</p>
<p align="center">
An open-source MCP (Model Context Protocol) server that gives AI assistants <strong>full access to Helm</strong> — the Kubernetes package manager. Built with the native Helm Go SDK, supporting both Helm 3.x and 4.x in a single binary.
</p>
<p align="center">
<em>Use natural language to manage your Kubernetes deployments.</em><br>
Connect helm-mcp to Claude, Cursor, VS Code, or any MCP-compatible client to install charts, manage releases, search repositories, and more — all through conversation.
</p>
---
## Table of Contents
- [Why helm-mcp?](#why-helm-mcp)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [MCP Client Configuration](#mcp-client-configuration)
- [MCP Protocol Support](#mcp-protocol-support)
- [Available Tools](#available-tools-46)
- [Tool Annotations](#tool-annotations)
- [Structured Output](#structured-output)
- [Helm CLI Coverage](#helm-cli-coverage)
- [Kubernetes Authentication](#kubernetes-authentication)
- [Helm Version Selection](#helm-version-selection)
- [Python Package](#python-package)
- [Resilience Configuration (Python)](#resilience-configuration-python)
- [Response Payload Management](#response-payload-management)
- [Known Limitations](#known-limitations)
- [Security](#security)
- [Authentication (OIDC/OAuth2)](#authentication-oidcoauth2)
- [On-Behalf-Of (OBO) Token Exchange](#on-behalf-of-obo-token-exchange)
- [Development](#development)
- [Architecture](#architecture)
- [Contributing](#contributing)
- [Community](#community)
- [License](#license)
## Why helm-mcp?
- **46 MCP tools** covering every Helm CLI command (minus shell completion and help)
- **Dual Helm SDK support** — Helm v3 and v4 via native Go SDK (not CLI wrappers)
- **Two transport modes** — stdio (default) and Streamable HTTP, stateless by default
- **Cloud provider ready** — EKS, GKE, AKS kubeconfig formats work out of the box
- **Security first** — Linux process hardening, credential memory zeroing, input validation, path traversal prevention
- **Python wrapper** — [FastMCP](https://github.com/PrefectHQ/fastmcp)-based proxy that auto-discovers all tools
- **Forward proxy support** — respects `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`
## Installation
### Pre-built Binaries
Download the latest release for your platform from [GitHub Releases](https://github.com/SCGIS-Wales/helm-mcp/releases):
```bash
# macOS (Apple Silicon)
curl -LO https://github.com/SCGIS-Wales/helm-mcp/releases/latest/download/helm-mcp-darwin-arm64
chmod +x helm-mcp-darwin-arm64
sudo mv helm-mcp-darwin-arm64 /usr/local/bin/helm-mcp
# macOS (Intel)
curl -LO https://github.com/SCGIS-Wales/helm-mcp/releases/latest/download/helm-mcp-darwin-amd64
chmod +x helm-mcp-darwin-amd64
sudo mv helm-mcp-darwin-amd64 /usr/local/bin/helm-mcp
# Linux (amd64)
curl -LO https://github.com/SCGIS-Wales/helm-mcp/releases/latest/download/helm-mcp-linux-amd64
chmod +x helm-mcp-linux-amd64
sudo mv helm-mcp-linux-amd64 /usr/local/bin/helm-mcp
# Linux (arm64)
curl -LO https://github.com/SCGIS-Wales/helm-mcp/releases/latest/download/helm-mcp-linux-arm64
chmod +x helm-mcp-linux-arm64
sudo mv helm-mcp-linux-arm64 /usr/local/bin/helm-mcp
```
### Build from Source
Requires Go 1.27+.
```bash
git clone https://github.com/SCGIS-Wales/helm-mcp.git
cd helm-mcp
make build
```
### Docker
```bash
docker build -t helm-mcp .
docker run -v ~/.kube:/home/helmuser/.kube:ro helm-mcp --mode stdio
```
### Python Package
```bash
pip install helm-mcp
```
See [Python Package](#python-package) below for full details.
## Quick Start
### stdio mode (for Claude Code, Cursor, etc.)
```bash
helm-mcp --mode stdio
```
### HTTP mode (Streamable HTTP)
```bash
helm-mcp --mode http
```
HTTP mode listens on `127.0.0.1:8080` by default. To accept remote clients,
configure authentication (see [Authentication](#authentication-oidcoauth2))
and bind explicitly, for example `--addr 0.0.0.0:8080`.
HTTP mode is **stateless by default** (MCP `2026-07-28`): there is no
`initialize` handshake and no `Mcp-Session-Id` header, so the server can sit
behind a plain round-robin load balancer with no sticky sessions and no shared
session store. Clients speaking an older protocol revision still work — the SDK
negotiates down automatically.
If a client needs the legacy per-session behaviour:
```bash
helm-mcp --mode http --stateless=false
```
> **Removed in v0.2.0: `--mode sse`.** The HTTP+SSE transport has been
> deprecated by the MCP specification since `2025-03-26` and is formally
> Deprecated under the `2026-07-28` feature lifecycle policy. Use
> `--mode http`, which serves Streamable HTTP on the same address.
## MCP Client Configuration
### Claude Desktop
Add to `~/.claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"helm": {
"command": "helm-mcp",
"args": ["--mode", "stdio"]
}
}
}
```
### Claude Code
```bash
claude mcp add helm -- helm-mcp --mode stdio
```
### Cursor / Windsurf / VS Code
Add to your MCP server configuration:
```json
{
"helm-mcp": {
"command": "helm-mcp",
"args": ["--mode", "stdio"]
}
}
```
### Remote / HTTP Clients
Start the server in HTTP mode, then connect any MCP-compatible client to the endpoint:
```bash
helm-mcp --mode http
# MCP endpoint: http://localhost:8080/mcp
```
## MCP Protocol Support
helm-mcp implements MCP **`2026-07-28`** and negotiates down to earlier
revisions (`2025-11-25`, `2025-06-18`, `2025-03-26`, `2024-11-05`) for older
clients, so upgrading the server does not require upgrading your client.
| Feature | Status |
|---|---|
| Tools | Supported — 46 tools, with titles, annotations and output schemas |
| Stateless transport | Supported and the default for `--mode http` (`--stateless=false` to opt out) |
| `server/discover` | Supported |
| Deterministic `tools/list` order | Supported — tools are returned sorted by name so clients and prompt caches keep hitting |
| Server instructions | Supported — the server explains version selection, cluster targeting and destructive-tool safety |
| Resources / Prompts | Not applicable — every Helm operation is a tool call |
| Tasks extension | Not implemented. Attractive for long `helm install --wait` runs, but client support is still thin. Tracked as a follow-up. |
| MRTR / Elicitation | Not implemented, deliberately. Helm tools take all their input as arguments, so there is no mid-call question to ask. |
| Roots / Sampling / Logging | Not implemented. All three are deprecated as of `2026-07-28` (SEP-2577). |
| HTTP+SSE transport | **Removed in v0.2.0** — deprecated by the specification. Use `--mode http`. |
## Available Tools (46)
### Release Management (14)
| Tool | Description |
|------|-------------|
| `helm_install` | Install a Helm chart as a new release |
| `helm_upgrade` | Upgrade a release to a new chart version or values |
| `helm_uninstall` | Uninstall a release and remove associated resources |
| `helm_rollback` | Rollback a release to a previous revision |
| `helm_list` | List releases (supports filters, sorting, pagination) |
| `helm_status` | Display release status, revision, chart, and values |
| `helm_history` | Show revision history of a release |
| `helm_test` | Run the test suite for a release |
| `helm_get_all` | Get all info (values, manifest, hooks, notes) for a release |
| `helm_get_hooks` | Get hooks for a release |
| `helm_get_manifest` | Get the Kubernetes manifest for a release |
| `helm_get_metadata` | Get metadata for a release |
| `helm_get_notes` | Get notes for a release |
| `helm_get_values` | Get values for a release (user-supplied or computed) |
### Chart Management (14)
| Tool | Description |
|------|-------------|
| `helm_create` | Create a new chart with the given name |
| `helm_lint` | Lint a chart for issues and best practices |
| `helm_template` | Render templates locally without installing |
| `helm_package` | Package a chart directory into an archive (.tgz) |
| `helm_pull` | Download a chart from a repository or OCI registry |
| `helm_push` | Push a chart archive to an OCI registry |
| `helm_verify` | Verify a chart has a valid provenance file |
| `helm_show_all` | Show all chart info (Chart.yaml, values, README, CRDs) |
| `helm_show_chart` | Show Chart.yaml of a chart |
| `helm_show_crds` | Show CRDs of a chart |
| `helm_show_readme` | Show README of a chart |
| `helm_show_values` | Show default values of a chart |
| `helm_dependency_build` | Build charts/ directory from Chart.lock |
| `helm_dependency_list` | List dependencies for a chart |
### Repository Management (5)
| Tool | Description |
|------|-------------|
| `helm_repo_add` | Add a chart repository |
| `helm_repo_list` | List configured chart repositories |
| `helm_repo_update` | Update chart repository indexes |
| `helm_repo_remove` | Remove chart repositories |
| `helm_repo_index` | Generate an index file for chart archives |
### Registry / OCI (2)
| Tool | Description |
|------|-------------|
| `helm_registry_login` | Login to an OCI registry |
| `helm_registry_logout` | Logout from an OCI registry |
### Search (2)
| Tool | Description |
|------|-------------|
| `helm_search_hub` | Search Artifact Hub for charts |
| `helm_search_repo` | Search locally configured repositories |
### Plugin Management (6)
| Tool | Description |
|------|-------------|
| `helm_plugin_install` | Install a Helm plugin |
| `helm_plugin_list` | List installed plugins |
| `helm_plugin_uninstall` | Uninstall a plugin |
| `helm_plugin_update` | Update a plugin |
| `helm_plugin_package` | Package a plugin directory into a signed archive (v4 only) |
| `helm_plugin_verify` | Verify a packaged plugin's signature and provenance (v4 only) |
### Environment (2)
| Tool | Description |
|------|-------------|
| `helm_env` | Print Helm environment information |
| `helm_version` | Print Helm SDK version information |
### Dependency Update (1)
| Tool | Description |
|------|-------------|
| `helm_dependency_update` | Update charts/ based on Chart.yaml |
## Tool Annotations
Every tool carries MCP annotations so a client — or an agent deciding what to
call — can tell a read at a glance from a change to your cluster.
| Annotation | Meaning | Tools |
|---|---|---|
| `readOnlyHint: true` | Only reads state; safe to call freely | `helm_list`, `helm_status`, `helm_history`, all `helm_get_*`, all `helm_show_*`, `helm_repo_list`, `helm_plugin_list`, `helm_dependency_list`, `helm_env`, `helm_version`, `helm_lint`, `helm_template`, `helm_verify`, `helm_plugin_verify`, `helm_search_*` |
| `destructiveHint: true` | Can remove or replace live state — call only on explicit user intent | `helm_uninstall`, `helm_rollback`, `helm_upgrade`, `helm_repo_remove`, `helm_plugin_uninstall` |
| `openWorldHint: true` | Reaches beyond the configured cluster (Artifact Hub, OCI registries, chart repos) | `helm_search_hub`, `helm_search_repo`, `helm_pull`, `helm_push`, all `helm_show_*`, `helm_template`, `helm_registry_*` |
| `idempotentHint: true` | Repeating the call with the same arguments has no additional effect | all read-only tools, plus `helm_repo_add`, `helm_repo_update`, `helm_dependency_*`, `helm_plugin_update` |
Everything not listed as destructive is annotated `destructiveHint: false`,
which matters because the MCP specification defaults that hint to `true`.
Annotations are hints, not a security boundary — a client may ignore them. For
a real boundary, use the OIDC scopes and roles described under
[Security](#security).
## Structured Output
Eleven tools publish an `outputSchema` and return `structuredContent` alongside
the human-readable text, so clients can consume results without parsing prose:
`helm_list`, `helm_status`, `helm_history`, `helm_get_metadata`,
`helm_get_values`, `helm_repo_list`, `helm_search_repo`, `helm_search_hub`,
`helm_plugin_list`, `helm_env`, `helm_version`.
```jsonc
// helm_list
{
"releases": [
{ "name": "my-app", "namespace": "default", "revision": 3, "status": "deployed", ... }
],
"count": 1
}
```
The existing JSON text content is unchanged, so clients that parse it keep
working. Collections are wrapped in an object with a `count` rather than
returned as a bare array. Tools whose output is free-form YAML or text —
`helm_template`, `helm_lint`, `helm_get_manifest`, `helm_show_*`,
`helm_package`, `helm_dependency_list` — have no output schema, because there
is no useful shape to declare.
## Helm CLI Coverage
Complete mapping of every `helm` CLI command to its helm-mcp MCP tool equivalent.
| Helm Command | MCP Tool | Status |
|---|---|---|
| `helm create` | `helm_create` | Covered |
| `helm dependency build` | `helm_dependency_build` | Covered |
| `helm dependency list` | `helm_dependency_list` | Covered |
| `helm dependency update` | `helm_dependency_update` | Covered |
| `helm env` | `helm_env` | Covered |
| `helm get all` | `helm_get_all` | Covered |
| `helm get hooks` | `helm_get_hooks` | Covered |
| `helm get manifest` | `helm_get_manifest` | Covered |
| `helm get metadata` | `helm_get_metadata` | Covered |
| `helm get notes` | `helm_get_notes` | Covered |
| `helm get values` | `helm_get_values` | Covered |
| `helm history` | `helm_history` | Covered |
| `helm install` | `helm_install` | Covered |
| `helm lint` | `helm_lint` | Covered |
| `helm list` | `helm_list` | Covered |
| `helm package` | `helm_package` | Covered |
| `helm plugin install` | `helm_plugin_install` | Covered |
| `helm plugin list` | `helm_plugin_list` | Covered |
| `helm plugin uninstall` | `helm_plugin_uninstall` | Covered |
| `helm plugin package` | `helm_plugin_package` | Covered (v4 only) |
| `helm plugin update` | `helm_plugin_update` | Covered |
| `helm plugin verify` | `helm_plugin_verify` | Covered (v4 only) |
| `helm pull` | `helm_pull` | Covered |
| `helm push` | `helm_push` | Covered |
| `helm registry login` | `helm_registry_login` | Covered |
| `helm registry logout` | `helm_registry_logout` | Covered |
| `helm repo add` | `helm_repo_add` | Covered |
| `helm repo index` | `helm_repo_index` | Covered |
| `helm repo list` | `helm_repo_list` | Covered |
| `helm repo remove` | `helm_repo_remove` | Covered |
| `helm repo update` | `helm_repo_update` | Covered |
| `helm rollback` | `helm_rollback` | Covered |
| `helm search hub` | `helm_search_hub` | Covered |
| `helm search repo` | `helm_search_repo` | Covered |
| `helm show all` | `helm_show_all` | Covered |
| `helm show chart` | `helm_show_chart` | Covered |
| `helm show crds` | `helm_show_crds` | Covered |
| `helm show readme` | `helm_show_readme` | Covered |
| `helm show values` | `helm_show_values` | Covered |
| `helm status` | `helm_status` | Covered |
| `helm template` | `helm_template` | Covered |
| `helm test` | `helm_test` | Covered |
| `helm uninstall` | `helm_uninstall` | Covered |
| `helm upgrade` | `helm_upgrade` | Covered |
| `helm verify` | `helm_verify` | Covered |
| `helm version` | `helm_version` | Covered |
| `helm completion` | — | Not applicable (shell utility) |
| `helm help` | — | Not applicable (shell utility) |
**46 of 46** operational Helm commands are covered. The only excluded commands (`completion`, `help`) are shell utilities that have no meaning in an MCP context.
`helm plugin package` and `helm plugin verify` were introduced in Helm v4 alongside signed plugin distribution, so they require `helm_version: "v4"`.
## Kubernetes Authentication
Every tool accepts these authentication fields via the `GlobalInput`:
| Field | JSON Key | Description |
|-------|----------|-------------|
| Kubeconfig | `kubeconfig` | Path to kubeconfig file (defaults to `$KUBECONFIG` or `~/.kube/config`) |
| Context | `kube_context` | Kubernetes context name to use |
| API Server | `kube_apiserver` | Override the API server URL from kubeconfig |
| Bearer Token | `kube_token` | Bearer token for API authentication |
| TLS Server Name | `kube_tls_server_name` | Server name for TLS certificate validation |
| Insecure TLS | `kube_insecure_tls` | Skip TLS certificate verification |
| Namespace | `namespace` | Target Kubernetes namespace |
### EKS (AWS)
EKS uses exec-based authentication in kubeconfig. The standard kubeconfig generated by `aws eks update-kubeconfig` works out of the box:
```json
{
"kubeconfig": "/home/user/.kube/config",
"kube_context": "arn:aws:eks:us-east-1:123456789:cluster/my-cluster"
}
```
Or with direct token authentication:
```json
{
"kube_apiserver": "https://ABCDEF.gr7.us-east-1.eks.amazonaws.com",
"kube_token": "<bearer-token-from-aws-eks-get-token>"
}
```
### GKE (Google Cloud)
GKE kubeconfig generated by `gcloud container clusters get-credentials` works out of the box:
```json
{
"kubeconfig": "/home/user/.kube/config",
"kube_context": "gke_my-project_us-central1_my-cluster"
}
```
### AKS (Azure)
AKS kubeconfig generated by `az aks get-credentials` works out of the box:
```json
{
"kubeconfig": "/home/user/.kube/config",
"kube_context": "my-aks-cluster"
}
```
## Helm Version Selection
Every tool supports a `helm_version` field to select between Helm v3 and v4:
```json
{
"helm_version": "v4",
"release_name": "my-release"
}
```
- `"v4"` (default) — Uses Helm v4 SDK with Server-Side Apply, WASM plugins, label selectors
- `"v3"` — Uses Helm v3 SDK for backward compatibility
### v4-Only Features
These fields are only available when using `helm_version: "v4"`. Passing one
with `"v3"` is an error rather than a silent no-op.
- `server_side_apply` — Use Kubernetes server-side apply
- `take_ownership` — Skip Helm annotation checks
- `hide_secret` — Hide secrets in dry-run output
- `force_conflicts` — Force conflict resolution
- `selector` — Label selector for list operations
- `show_resources` — Show resources table in status
- `reset_then_reuse_values` — Reset then reuse values in upgrade
- `wait_strategy` — See below
Two tools are v4-only in their entirety: `helm_plugin_package` and
`helm_plugin_verify`.
### Waiting: `wait` and `wait_strategy`
Helm v4 replaced v3's boolean `--wait` with a strategy enum, so
`helm_install`, `helm_upgrade`, `helm_rollback` and `helm_uninstall` accept a
`wait_strategy` field alongside `wait`:
| `wait_strategy` | Behaviour |
|---|---|
| `"watcher"` | Wait using kstatus — what `helm --wait` does in v4 |
| `"legacy"` | Wait using the v3 readiness checks |
| `"hookOnly"` | Only wait for hooks; the CLI default when `--wait` is omitted |
`wait_strategy` takes precedence when set. Otherwise `wait: true` maps to
`"watcher"` and `wait: false` maps to `"hookOnly"`.
> Before v0.2.0 the v4 engine hardcoded the watcher strategy, so `wait: false`
> was silently ignored and every v4 operation waited. If you relied on that
> behaviour, set `wait: true` (or `wait_strategy: "watcher"`) explicitly.
### Available on both v3 and v4
`rollback_on_failure` (Helm's `--atomic`) is supported by both engines, and is
available on `helm_install` and `helm_upgrade`. So are `devel`, `sub_notes`,
`hide_notes`, `skip_schema_validation`, `disable_openapi_validation` and
`enable_dns`; `output_dir` and `use_release_name` on `helm_install`;
`ignore_not_found` and `description` on `helm_uninstall`; and `all`,
`uninstalling` and `time_format` on `helm_list`.
## Python Package
A Python wrapper is available that uses [FastMCP](https://github.com/PrefectHQ/fastmcp) to create a transparent proxy around the helm-mcp Go binary. New tools added to the Go binary are automatically available in Python without code changes.
### Installation
```bash
pip install helm-mcp
```
Requires Python 3.12+. The Go binary is **bundled inside platform-specific wheels** — no Go toolchain is required. Supported platforms: `linux-amd64`, `linux-arm64`, `darwin-amd64`, `darwin-arm64`, `windows-amd64`. The binary is extracted from the wheel on first use, with SHA256 checksum verification to protect against tampering.
You can verify the binary is available:
```bash
helm-mcp-python --setup
```
### Usage as a Server
```python
from helm_mcp import create_server
# stdio mode (default, for MCP clients)
server = create_server()
server.run()
# HTTP mode
server = create_server()
server.run(transport="http", host="127.0.0.1", port=8080)
```
### Usage as a Client
```python
import asyncio
from helm_mcp import create_client
async def main():
async with create_client() as client:
# List all available tools
tools = await client.list_tools()
print(f"Available tools: {len(tools)}")
# List Helm releases
result = await client.call_tool("helm_list", {"namespace": "default"})
print(result)
# Install a chart
result = await client.call_tool("helm_install", {
"release_name": "my-app",
"chart": "bitnami/nginx",
"namespace": "default",
})
print(result)
asyncio.run(main())
```
### CLI
```bash
# stdio mode (for MCP clients like Claude Code)
helm-mcp-python
# HTTP mode (loopback only by default; the Python proxy has no authentication)
helm-mcp-python --transport http --port 8080
# Bind to all interfaces only behind your own authenticating proxy
helm-mcp-python --transport http --host 0.0.0.0 --allow-remote
# Custom binary path
helm-mcp-python --binary /usr/local/bin/helm-mcp
```
### Integrating with FastMCP
The Python package is built on [FastMCP](https://github.com/PrefectHQ/fastmcp) and returns standard FastMCP server/client objects. You can compose it with other FastMCP servers:
```python
from fastmcp import FastMCP
from helm_mcp import create_server as create_helm_server
# Create a composite server
app = FastMCP("my-platform")
# Mount helm-mcp as a sub-server
helm = create_helm_server()
app.mount("helm", helm)
# Add your own tools alongside Helm
@app.tool()
def my_custom_tool(param: str) -> str:
return f"Custom: {param}"
app.run()
```
### Binary Discovery
The Python package locates the `helm-mcp` Go binary in this order:
1. `HELM_MCP_BINARY` environment variable
2. Bundled binary in the package `bin/` directory
3. Auto-download from GitHub Releases (with SHA256 checksum verification)
4. `helm-mcp` on `PATH`
### Environment Variables
The proxy forwards these environment variables to the Go subprocess:
| Category | Variables |
|----------|-----------|
| Proxy | `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` (and lowercase variants) |
| Kubernetes | `KUBECONFIG`, `KUBERNETES_SERVICE_HOST`, `KUBERNETES_SERVICE_PORT` |
| Helm | `HELM_CACHE_HOME`, `HELM_CONFIG_HOME`, `HELM_DATA_HOME`, `HELM_PLUGINS`, `HELM_DEBUG` |
| AWS | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`, `AWS_REGION`, `AWS_PROFILE` |
| GCP | `GOOGLE_APPLICATION_CREDENTIALS`, `CLOUDSDK_COMPUTE_ZONE` |
| Azure | `AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_SUBSCRIPTION_ID` |
| TLS | `SSL_CERT_FILE`, `SSL_CERT_DIR` |
## Resilience Configuration (Python)
The Python package includes a comprehensive resilience stack built on [FastMCP](https://github.com/PrefectHQ/fastmcp) middleware, [circuitbreaker](https://pypi.org/project/circuitbreaker/), and [tenacity](https://pypi.org/project/tenacity/). All settings are configurable via `HELM_MCP_*` environment variables with sensible defaults.
### Environment Variables
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| `HELM_MCP_RETRY_ENABLED` | bool | `true` | Enable proxy-level retry middleware |
| `HELM_MCP_RETRY_MAX_RETRIES` | int | `2` | Maximum retry attempts |
| `HELM_MCP_RETRY_BASE_DELAY` | float | `1.0` | Initial backoff delay (seconds) |
| `HELM_MCP_RETRY_MAX_DELAY` | float | `30.0` | Maximum backoff delay (seconds) |
| `HELM_MCP_RETRY_BACKOFF_MULTIPLIER` | float | `2.0` | Backoff multiplier |
| `HELM_MCP_RATE_LIMIT_ENABLED` | bool | `false` | Enable token-bucket rate limiting |
| `HELM_MCP_RATE_LIMIT_MAX_RPS` | float | `10.0` | Maximum requests per second |
| `HELM_MCP_RATE_LIMIT_BURST` | int | `20` | Burst capacity |
| `HELM_MCP_CACHE_ENABLED` | bool | `false` | Enable TTL-based response caching |
| `HELM_MCP_CACHE_TOOL_TTL` | int | `300` | Tool call cache TTL (seconds) |
| `HELM_MCP_CACHE_LIST_TTL` | int | `60` | Tool list cache TTL (seconds) |
| `HELM_MCP_ERROR_HANDLING_ENABLED` | bool | `true` | Enable structured error responses |
| `HELM_MCP_ERROR_INCLUDE_TRACEBACK` | bool | `false` | Include tracebacks in errors |
| `HELM_MCP_TIMING_ENABLED` | bool | `true` | Enable request timing |
| `HELM_MCP_TIMING_DETAILED` | bool | `false` | Use detailed timing middleware |
| `HELM_MCP_CIRCUIT_BREAKER_ENABLED` | bool | `true` | Enable circuit breaker on tool calls |
| `HELM_MCP_CIRCUIT_BREAKER_FAILURE_THRESHOLD` | int | `5` | Failures before circuit opens |
| `HELM_MCP_CIRCUIT_BREAKER_RESET_TIMEOUT` | float | `30.0` | Seconds before half-open retry |
| `HELM_MCP_TENACITY_ENABLED` | bool | `true` | Enable tenacity retry with jitter |
| `HELM_MCP_TENACITY_MAX_ATTEMPTS` | int | `3` | Maximum retry attempts |
| `HELM_MCP_TENACITY_MIN_WAIT` | float | `0.5` | Minimum wait between retries (seconds) |
| `HELM_MCP_TENACITY_MAX_WAIT` | float | `10.0` | Maximum wait between retries (seconds) |
| `HELM_MCP_TENACITY_MULTIPLIER` | float | `1.5` | Exponential backoff base |
| `HELM_MCP_BULKHEAD_ENABLED` | bool | `true` | Enable concurrency limiter |
| `HELM_MCP_BULKHEAD_MAX_CONCURRENT` | int | `10` | Maximum concurrent tool calls |
| `HELM_MCP_OTEL_ENABLED` | bool | `false` | Enable OpenTelemetry tracing |
| `HELM_MCP_OTEL_SERVICE_NAME` | str | `helm-mcp` | OTel service name |
| `HELM_MCP_OTEL_EXPORTER` | str | `console` | OTel exporter (`console` or `otlp`) |
### CLI Flags
```bash
helm-mcp-python --no-retry # Disable proxy retry middleware
helm-mcp-python --rate-limit 50 # Enable rate limiting at 50 rps
helm-mcp-python --cache # Enable response caching
helm-mcp-python --no-circuit-breaker # Disable circuit breaker
helm-mcp-python --bulkhead-max 5 # Limit to 5 concurrent tool calls
helm-mcp-python --otel # Enable OpenTelemetry tracing
```
### Programmatic Configuration
```python
from helm_mcp import create_server, HelmClient
from helm_mcp.resilience import (
ResilienceConfig,
RateLimitConfig,
CircuitBreakerConfig,
BulkheadConfig,
)
# Server with custom resilience
config = ResilienceConfig(
rate_limit=RateLimitConfig(enabled=True, max_requests_per_second=50),
circuit_breaker=CircuitBreakerConfig(failure_threshold=3),
bulkhead=BulkheadConfig(max_concurrent=20),
)
server = create_server(resilience=config)
# Client with custom resilience
async with HelmClient(resilience=config) as helm:
releases = await helm.list(namespace="default")
```
### OpenTelemetry
FastMCP emits traces via the OpenTelemetry API. To receive actual trace data, install the SDK:
```bash
pip install helm-mcp[otel]
```
Then enable tracing:
```bash
export HELM_MCP_OTEL_ENABLED=true
export HELM_MCP_OTEL_EXPORTER=otlp # or "console"
export HELM_MCP_OTEL_SERVICE_NAME=helm-mcp
```
## Response Payload Management
Large Helm outputs (manifests, values, template renders) can overflow LLM context windows. helm-mcp includes two layers of response size management to prevent this.
### Response Truncation
All tool responses are automatically truncated when they exceed a configurable size limit. The default is **256 KB** (~64K tokens). Truncated responses include metadata indicating the original size and a suggestion to use more specific queries.
Configure the limit via CLI flag or environment variable:
```bash
# CLI flag (in bytes, 0 to disable)
helm-mcp --mode stdio --max-response-bytes 524288
# Environment variable
export HELM_MCP_MAX_RESPONSE_BYTES=524288
helm-mcp --mode stdio
```
The CLI flag takes precedence over the environment variable.
### Manifest Sanitisation
Tools that return Kubernetes YAML (`helm_get_manifest`, `helm_get_all`, `helm_get_hooks`, `helm_template`) automatically strip noisy fields before returning results. This typically reduces manifest sizes by **40-60%** without losing meaningful information.
**Fields stripped:**
- `metadata.managedFields` — internal Kubernetes bookkeeping (often the largest single field)
- `kubectl.kubernetes.io/last-applied-configuration` — redundant copy of the entire object
- `deployment.kubernetes.io/revision` — internal controller annotation
- `control-plane.alpha.kubernetes.io/leader` — leader election data
This sanitisation is always active and cannot be disabled, as these fields are never useful for LLM interactions. The original unsanitised data remains available through direct `kubectl` access.
### Resilience Primitives
The `internal/resilience` package provides additional production resilience patterns:
| Pattern | Description |
|---------|-------------|
| **Circuit breaker** | Three-state (Closed/Open/HalfOpen) pattern to fail fast when backends are unavailable. Configurable failure threshold and recovery timeout. |
| **Retry with backoff** | Exponential backoff with jitter for transient failures. Context-aware cancellation and retryable error filtering. |
| **Per-tool timeouts** | Category-based default timeouts: query (30s), mutate (120s), chart (60s), repo (60s). Respects existing context deadlines. |
## Known Limitations
### Plugin Verification Required (Helm v4 CLI)
All six plugin tools shell out to the system `helm` CLI, which is the only part
of helm-mcp that does not go through the Helm Go SDK — Helm's plugin packages
are internal and not importable. Helm v4 requires plugin source verification by
default, and plugins that do not support it (like `helm-diff`) need
`--verify=false`, which the MCP tools do not yet expose.
- **Workaround**: install those plugins directly via `helm plugin install <url> --verify=false`
- The published container image ships a pinned `helm` CLI, so plugin tools work
there. If you run the binary directly, `helm` must be on `PATH`.
## Security
### Process Hardening (Linux)
When running on Linux, helm-mcp applies process-level hardening at startup to reduce the attack surface of the stdio transport. MCP servers running as IDE child processes inherit full user privileges — these mitigations limit what an attacker can do if the process is compromised.
| Mechanism | What It Does |
|-----------|-------------|
| **PR_SET_DUMPABLE(0)** | Blocks `ptrace` attach, core dumps, and `/proc/pid/mem` reads. Prevents other processes from inspecting credentials in memory. |
| **Capability dropping** | Drops all Linux capabilities from the bounding set. No-op for non-root users (the common case), but protects against privilege escalation when running in misconfigured Docker/Kubernetes environments. |
| **Credential memory zeroing** | `ZeroCredentials()` is called via `defer` after every tool handler completes, overwriting bearer tokens and passwords in memory. This is defense-in-depth — Go strings are immutable and the GC may retain copies, but it reduces the credential lifetime in our code paths. |
Hardening is **best-effort and non-fatal** — failures are logged (with `--debug`) but never crash the process. On non-Linux platforms (macOS, Windows), hardening is skipped with an informational log message.
```bash
# Verify hardening is active (Linux)
helm-mcp --mode stdio --debug 2>&1 | grep "security hardening"
# Disable for debugging (e.g., when using strace or delve)
helm-mcp --mode stdio --no-harden
```
#### Mechanisms Evaluated but Not Implemented
| Mechanism | Why Skipped |
|-----------|-------------|
| **Seccomp BPF** | The server uses `exec.CommandContext` for plugins and network I/O for Kubernetes API and registries. The syscall surface is too wide to filter safely without breaking Helm SDK internals across kernel versions. |
| **Namespace isolation** | The process needs access to `~/.kube/config`, cloud credential files, DNS, and network. Namespace isolation would break core functionality. |
| **Cgroup resource limits** | A 5-minute `pluginExecTimeout` already bounds runaway operations, and the IDE manages process lifecycle. |
| **AppArmor/SELinux profiles** | High maintenance burden for dynamic file paths. Better deployed as an external artifact, not embedded in the binary. |
### Credential Scrubbing
All error messages are automatically scrubbed to remove:
- Bearer tokens (including EKS, GKE, and Azure JWT tokens)
- Basic authentication credentials
- URL-embedded passwords (`https://user:password@host`)
### Input Validation
Every tool handler calls `ValidateGlobalInput` before executing, ensuring namespace and kubeconfig fields are validated on every request.
The security package provides validators for:
- Release names (DNS-1123 compliant)
- Namespace names
- Kubeconfig file paths (path traversal prevention, symlink detection, sensitive path rejection — `/etc/shadow`, `/proc/`, `/dev/`, `/sys/` are blocked)
- URLs (scheme validation + **SSRF protection**: DNS resolution with private IP blocking for `0.0.0.0/8`, `127.0.0.0/8`, `10.0.0.0/8`, `100.64.0.0/10`, `172.16.0.0/12`, `192.168.0.0/16`, `169.254.0.0/16`, IPv4-mapped addresses, NAT64, and IPv6 unspecified/loopback/link-local/unique-local ranges). Applied to repository URLs, URL chart references, remote values files, and OCI push targets
- File paths (traversal prevention, symlink rejection including symlinked parent directories, sensitive path rejection). Applied to values files, keyrings, CA/cert/key files, passphrase files, and output directories
- Repository names (alphanumeric, dots, dashes, underscores; prevents cache path traversal)
- Timeout durations (max 24h)
- Plugin names (alphanumeric + dashes/underscores, no leading dash to prevent argument injection)
### File Permissions
- Repository configuration files are written with `0600` (owner read/write only)
- Config directories are created with `0700` (owner only)
### HTTP Server Hardening
When running in HTTP mode:
- Listens on `127.0.0.1:8080` unless `--addr` says otherwise
- `ReadHeaderTimeout: 10s` and `ReadTimeout: 30s` — prevent slow client attacks
- `WriteTimeout: 30m` (`--write-timeout`) — long enough for `helm_install`/`helm_upgrade` with `wait`; raise it if clients use longer Helm timeouts
- `IdleTimeout: 120s` — reclaims idle connections
- `MaxHeaderBytes: 1MB` — prevents header-based DoS
- Graceful shutdown with 5-second timeout
### Authentication (OIDC/OAuth2)
When running in HTTP mode, helm-mcp supports OAuth2/OIDC authentication with JWT validation, claims-based authorization, and structured audit logging. This aligns with the [MCP Security Best Practices](https://modelcontextprotocol.io/specification/2025-11-25/basic/security_best_practices).
**Authentication is fully opt-in.** When no OIDC or token environment variables are set, the server runs without authentication (same as previous versions). Stdio mode is never affected by authentication configuration.
#### Quick Start — Entra ID (Azure AD / ADFS)
```bash
# Required: issuer and audience
export HELM_MCP_OIDC_ISSUER="https://login.microsoftonline.com/{tenant-id}/v2.0"
export HELM_MCP_OIDC_AUDIENCE="api://helm-mcp-server"
# Optional: restrict access by scopes, roles, or client app IDs
export HELM_MCP_REQUIRED_SCOPES="helm.read,helm.write"
export HELM_MCP_REQUIRED_ROLES="HelmOperator"
export HELM_MCP_ALLOWED_CLIENTS="client-app-id-1,client-app-id-2"
# Optional: explicit JWKS URL (auto-discovered from issuer if omitted)
export HELM_MCP_OIDC_JWKS_URL="https://login.microsoftonline.com/{tenant-id}/discovery/v2.0/keys"
helm-mcp --mode http --addr 0.0.0.0:8080
```
Authentication events (`auth_success`, `auth_failure`) are written to stderr as JSON at Info level, independent of `--debug`.
#### Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `HELM_MCP_OIDC_ISSUER` | Yes (for OIDC) | OIDC issuer URL. Token `iss` claim must match. |
| `HELM_MCP_OIDC_AUDIENCE` | Yes (for OIDC) | Expected `aud` claim. Tokens issued for other resources are rejected. |
| `HELM_MCP_OIDC_JWKS_URL` | No | JWKS endpoint for signature verification. Auto-discovered from issuer if omitted. |
| `HELM_MCP_REQUIRED_SCOPES` | No | Comma-separated OAuth2 scopes required in the `scp` claim. |
| `HELM_MCP_REQUIRED_ROLES` | No | Comma-separated app roles required in the `roles` claim. |
| `HELM_MCP_ALLOWED_CLIENTS` | No | Comma-separated client app IDs allowed in `azp`/`appid` claim. |
| `HELM_MCP_SESSION_TTL` | No | Session cache inactivity TTL (Go duration, e.g., `5m`, `15m`). Default: `5m`. |
| `HELM_MCP_AUTH_TOKEN` | No | Static bearer token (legacy). Lower priority than OIDC. |
#### Authentication Priority
1. **OIDC/OAuth2** — if `HELM_MCP_OIDC_ISSUER` is set, JWT validation with JWKS is enabled.
2. **Static bearer token** — if only `HELM_MCP_AUTH_TOKEN` is set, constant-time comparison is used.
3. **No auth** — if neither is set, the server accepts all requests (suitable for local stdio usage).
#### Token Validation
Every incoming JWT is validated for:
| Check | Description |
|-------|-------------|
| **Signature** | RSA signature verified against JWKS public keys (RS256/384/512). Keys are cached for 1 hour with automatic refresh on `kid` miss (key rotation). |
| **Issuer (`iss`)** | Must exactly match `HELM_MCP_OIDC_ISSUER`. |
| **Audience (`aud`)** | Must match `HELM_MCP_OIDC_AUDIENCE`. Tokens issued for other APIs are rejected — this is the core defense against token passthrough. |
| **Expiry (`exp`)** | Required. Expired tokens are rejected. |
| **Authorized Party (`azp`/`appid`)** | Checked against `HELM_MCP_ALLOWED_CLIENTS` if configured. Supports both OIDC `azp` and Entra ID v1 `appid` claims. |
| **Scopes (`scp`)** | Space-separated scopes checked against `HELM_MCP_REQUIRED_SCOPES`. |
| **Roles (`roles`)** | Array of app roles checked against `HELM_MCP_REQUIRED_ROLES`. |
#### Session Cache
Validated tokens are cached in-memory to avoid redundant JWKS lookups:
- **Inactivity TTL**: 5 minutes by default (configurable via `HELM_MCP_SESSION_TTL`, e.g., `5m`, `10m`, `1h`)
- **Token expiry**: Cached tokens are never used beyond their `exp` claim
- **Cache key**: SHA-256 hash of the raw bearer token (prevents raw token storage in memory)
- **Sliding window**: Each access resets the inactivity timer
- **Maximum entries**: 10,000 (oldest evicted on overflow)
#### Audit Logging
When OIDC authentication is enabled, structured audit events are emitted via `slog` for every authentication attempt:
```
level=INFO msg=security_audit audit.event_type=auth_success audit.principal_id=oid-123 audit.principal_name=user@example.com audit.tenant_id=tenant-abc audit.client_app_id=client-1 audit.scopes="helm.read helm.write" audit.token_id=uti-xyz audit.remote_addr=10.0.0.1:54321
```
Audit events include: principal ID/name, tenant ID, client app ID, scopes, roles, token ID, session ID, action, resource, result, duration, and remote address. Enable `--debug` for full audit visibility, or configure your log aggregator to capture `security_audit` messages.
### On-Behalf-Of (OBO) Token Exchange
When helm-mcp needs to call a downstream API (such as the Kubernetes API) on behalf of the authenticated user, it exchanges the incoming token for a new one scoped to that downstream service. This avoids forwarding the original token, which could be misused if the downstream service is compromised or if the token's audience doesn't match.
#### How It Works
```
User MCP Client helm-mcp (MCP Server) Kubernetes API
│ │ │ │
├─(SSO)──────▶│ gets token │ │
│ │ aud=helm-mcp │ │
│ ├─(Bearer token)──────▶│ │
│ │ ├─OBO exchange──────────▶│
│ │ │ grant_type=jwt-bearer │
│ │ │ assertion=user token │
│ │ │ scope=K8s API scopes │
│ │ │◀─new token──────────────│
│ │ │ aud=Kubernetes API │
│ │ ├─(K8s API call)────────▶│
│ │ │ with OBO token │
```
Each hop in the chain:
1. Validates the incoming token's audience (must match this server)
2. Exchanges it via OBO for a new token targeted at the next service
3. Preserves the original user's identity in the new token's claims
4. Triggers a fresh Conditional Access evaluation (if configured in Entra ID)
#### OBO Configuration
```bash
# OBO token exchange (for downstream API calls with user context)
export HELM_MCP_OBO_TOKEN_URL="https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token"
export HELM_MCP_OBO_CLIENT_ID="helm-mcp-app-id"
export HELM_MCP_OBO_CLIENT_SECRET="helm-mcp-client-secret"
```
#### Why Not Forward the Token?
Forwarding a user's token to downstream services is tempting but problematic. The [MCP Security Best Practices](https://modelcontextprotocol.io/specification/2025-11-25/basic/security_best_practices) explicitly discourages it. With OBO, each service gets a token minted for its own audience, scoped to only the permissions it needs. If a token is intercepted, the blast radius is limited to a single service rather than the entire chain.
### AWS EKS and OBO Support
#### AWS EKS 1.34+ with OIDC
AWS EKS supports OIDC identity providers for cluster authentication starting from EKS 1.21, with significant improvements in 1.34+. However, **EKS does not natively support the Entra ID OBO flow for Kubernetes API authentication**. The authentication patterns differ:
| Pattern | Supported | Details |
|---------|-----------|---------|
| **EKS OIDC Identity Provider** | Yes | Configure Entra ID as an OIDC provider in EKS. Users authenticate directly with Entra ID tokens where `aud` = EKS cluster. No OBO needed — the token is issued directly for the cluster. |
| **IRSA (IAM Roles for Service Accounts)** | Yes | Pod-level identity via projected service account tokens. This is M2M (client credentials), not user-delegated. |
| **EKS Pod Identity** | Yes (EKS 1.34+) | Simplified pod identity using EKS Pod Identity Agent. Also M2M, not user-delegated. |
| **OBO → Kubernetes API** | Partial | Entra ID OBO can issue tokens for any registered resource. If EKS is configured with Entra ID as an OIDC provider and the Kubernetes API is registered as an app in Entra ID, OBO-issued tokens can authenticate to EKS. Requires custom `--oidc-issuer-url`, `--oidc-client-id`, and `--oidc-username-claim` configuration on the EKS OIDC provider. |
**Recommended pattern for EKS**: Configure Entra ID as the EKS OIDC identity provider. The MCP client authenticates the user and obtains a token with `aud` = helm-mcp. helm-mcp validates this token, then performs an OBO exchange to get a new token with `aud` = EKS cluster OIDC client ID. This OBO-issued token is used for Kubernetes API calls, preserving user identity and enabling per-user RBAC.
#### AWS Labs MCP and OBO
AWS Labs MCP servers (e.g., `awslabs/mcp`) and Amazon Bedrock AgentCore **do not implement OAuth2 OBO or RFC 8693 Token Exchange**. AgentCore uses a different model:
- **User identity propagation**: Via an opaque `X-Amzn-Bedrock-AgentCore-Runtime-User-Id` HTTP header — **not** a cryptographically signed token
- **Outbound authentication**: OAuth Authorization Code (3Lo) or Client Credentials (2Lo), but these are separate auth events, not delegated identity propagation
- **Token Vault**: Stores refresh tokens for third-party services, but this is agent-scoped, not user-delegated
This means AWS Labs MCP servers cannot participate in an Entra ID OBO chain natively. If your architecture requires user-delegated identity propagation through AWS-hosted MCP servers, you must implement OBO exchange as a custom middleware layer, or use helm-mcp's built-in OBO support as a reference implementation.
### Forward Proxy Support
helm-mcp respects standard proxy environment variables:
```bash
export HTTP_PROXY=http://proxy.example.com:8080
export HTTPS_PROXY=http://proxy.example.com:8080
export NO_PROXY=localhost,127.0.0.1,.internal.company.com
```
## Development
### Prerequisites
- Go 1.27+
- Python 3.12+ (for the Python package)
- golangci-lint v2 (optional, for linting)
### Build
```bash
make build # Build binary
make install # Install to $GOPATH/bin
make build-all # Cross-compile for Linux/macOS (amd64/arm64)
```
### Test
```bash
# Go tests
make test # Run all tests with race detection and coverage
make test-short # Run tests without integration tests
# Python tests (33 tests)
cd python && pip install -e ".[dev]" && pytest -v tests/
```
### Lint
```bash
make lint # Run golangci-lint + go vet
make vet # Run go vet only
```
### Security Check
```bash
make security # Run govulncheck
```
### Coverage
```bash
make coverage # Generate coverage report (coverage.html)
```
## Architecture
```
cmd/helm-mcp/ Entry point, transport selection, CLI flags
internal/
helmengine/ Engine interface and shared types
v3/ Helm v3 SDK implementation
v4/ Helm v4 SDK implementation
tools/ MCP tool handlers
release/ Install, upgrade, uninstall, rollback, list, status, etc.
chart/ Create, lint, template, package, pull, push, show, etc.
repo/ Add, list, update, remove, index
registry/ Login, logout
search/ Hub, repo
plugin/ Install, list, uninstall, update
env/ Env, version
security/ Process hardening, input validation, credential scrubbing
resilience/ Response budget, circuit breaker, retry, timeouts, manifest sanitisation
server/ MCP server creation and tool registration
python/ FastMCP-based Python wrapper
src/helm_mcp/ Python package source
tests/ Python tests
```
## Contributing
We welcome contributions from the community! Whether it's bug reports, feature requests, documentation improvements, or code contributions — all help is appreciated.
See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines on:
- Setting up your development environment
- Running tests and linters
- Submitting pull requests
- Commit message conventions
## Community
- **Bug reports & feature requests**: [GitHub Issues](https://github.com/SCGIS-Wales/helm-mcp/issues)
- **Discussions & questions**: [GitHub Discussions](https://github.com/SCGIS-Wales/helm-mcp/discussions)
- **Releases**: [GitHub Releases](https://github.com/SCGIS-Wales/helm-mcp/releases) (auto-published on every merge to main)
## License
This project is licensed under the [MIT License](LICENSE) — free to use, modify, and distribute.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues