
matthisholleville/argocd-mcp
Developer toolsMCP server that exposes the entire ArgoCD API to LLMs all endpoints, not just a handful. Reads ArgoCD's OpenAPI spec at startup, zero hardcoded tools.
Overview
The entire ArgoCD API, exposed to LLMs via MCP. 103+ endpoints. Zero hardcoded handlers. Two modes: search or generated tools. Quick Start • How It Works • OAuth • OIDC • Configuration Most ArgoCD MCP servers hardcode a few operations: list apps, sync, get status. When ArgoCD adds a new feature, you wait for the maintainer to add it. takes a different approach, inspired by Cloudflare's MCP server which covers 2500+ endpoints with only 2 tools. It reads ArgoCD's OpenAPI spec at startup and exposes every endpoint through just 2 tools: search and execute. New ArgoCD version? Restart the server. Done.
README
argocd-mcp
The entire ArgoCD API, exposed to LLMs via MCP. 103+ endpoints. Zero hardcoded handlers. Two modes: search or generated tools.
Quick Start • How It Works • OAuth • OIDC • Configuration
Most ArgoCD MCP servers hardcode a few operations: list apps, sync, get status. When ArgoCD adds a new feature, you wait for the maintainer to add it.
argocd-mcp takes a different approach, inspired by Cloudflare’s MCP server which covers 2500+ endpoints with only 2 tools. It reads ArgoCD’s OpenAPI spec at startup and exposes every endpoint through just 2 tools: search and execute. New ArgoCD version? Restart the server. Done.
- 103+ endpoints from ArgoCD’s OpenAPI spec, zero hardcoded handlers
- Two tool modes:
search(2 meta-tools) orgenerated(1 typed tool per endpoint) - Works with Claude Desktop, Claude Code, Cursor, or any MCP client
- No code per endpoint — the OpenAPI spec is the source of truth
- Three auth modes: static token, OAuth via ArgoCD Dex, or OAuth via the external OIDC provider ArgoCD trusts (both give per-user RBAC)
- Read-only mode — disable all write operations with a single flag
- Resource scoping — restrict which ArgoCD resources are exposed with
ALLOWED_RESOURCES - Rate limiting — per-user token bucket to protect ArgoCD from excessive calls
- Prompt templates — pre-packaged workflows for common operations (unhealthy apps, diff, rollback, logs)
- Audit logging — structured JSON logs for every tool call (user, method, path, status, duration)
- MCP annotations — tools are annotated as read-only, destructive, or idempotent for proper client categorization
- Optional semantic search via Ollama embeddings
How It Works
At startup, the server fetches ArgoCD’s Swagger spec and parses every endpoint. Then it exposes them to LLMs via one of two modes:
Search mode (default, TOOL_MODE=search)
Two meta-tools handle all 103+ endpoints. The LLM discovers endpoints by searching, then calls them via a generic executor.
graph TD
A[ArgoCD /swagger.json] -->|Fetch at startup| B[Parse Swagger 2.0]
B --> C[103+ Endpoints in memory]
C --> D[search_operations]
C --> E[execute_operation]
D -->|LLM discovers endpoints| F[Returns method, path, summary, params]
E -->|LLM calls API| G[Proxies to ArgoCD with user token]
Generated mode (TOOL_MODE=generated)
One typed MCP tool per endpoint, generated dynamically at startup. The LLM calls argocd_application_sync(name, revision) directly — no search step, no path construction.
graph TD
A[ArgoCD /swagger.json] -->|Fetch at startup| B[Parse Swagger 2.0]
B --> C[103+ Endpoints]
C -->|Generate per endpoint| D[argocd_application_list]
C --> E[argocd_application_sync]
C --> F[argocd_cluster_get]
C --> G[... 100+ more tools]
D & E & F & G -->|Typed params, 1 call| H[Proxies to ArgoCD]
Which mode to choose?
| Search | Generated | |
|---|---|---|
| Tools registered | 2 | 103+ |
| LLM round-trips | 2 (search → execute) | 1 (direct call) |
| Parameter typing | Raw JSON strings | Typed individual params |
| Context usage | Low (~200 tokens) | Higher (mitigated by client deferred loading) |
| Best for | Lightweight clients, constrained context | Claude Code, Claude Desktop, Cursor |
Clients like Claude Code and Claude Desktop support deferred tool loading — they only load tool definitions into context when needed, so the 103+ tools don’t consume context window upfront.
Quick Start
Helm Chart (Kubernetes)
helm install argocd-mcp oci://ghcr.io/matthisholleville/charts/argocd-mcp \
--set argocd.baseURL=https://argocd.example.com \
--set argocd.token=your-token
See all configuration options in charts/argocd-mcp/values.yaml.
Static Token (simple)
Best for local dev, CI/CD, or single-user setups. Uses a static ArgoCD API token.
Claude Code
claude mcp add argocd -s user -- \
docker run --rm -i \
-e ARGOCD_BASE_URL=https://argocd.example.com \
-e ARGOCD_TOKEN=your-token \
ghcr.io/matthisholleville/argocd-mcp:latest
Claude Desktop
Add to your Claude Desktop MCP config (claude_desktop_config.json):
{
"mcpServers": {
"argocd": {
"command": "docker",
"args": ["run", "--rm", "-i",
"-e", "ARGOCD_BASE_URL=https://argocd.example.com",
"-e", "ARGOCD_TOKEN=your-token",
"ghcr.io/matthisholleville/argocd-mcp:latest"
]
}
}
}
OAuth via ArgoCD Dex (per-user RBAC)
Best for multi-user, production setups. Each user authenticates with their own identity via ArgoCD’s built-in Dex. No static token needed — the user’s Dex id_token is forwarded to ArgoCD, which applies its RBAC policies per user.
Step 1: Start the server
docker run -p 8080:8080 \
-e ARGOCD_BASE_URL=https://argocd.example.com \
-e MCP_TRANSPORT=http \
-e AUTH_MODE=oauth \
-e DEX_CLIENT_ID=argo-cd-cli \
-e SERVER_BASE_URL=http://localhost:8080 \
ghcr.io/matthisholleville/argocd-mcp:latest
Step 2: Connect your MCP client
Claude Code
claude mcp add --transport http --callback-port 9382 argocd http://localhost:8080/mcp
Then run /mcp inside Claude Code to authenticate via the browser.
Claude Desktop
Claude Desktop requires a publicly accessible URL (the OAuth redirect goes through claude.ai). Expose the server via a reverse proxy or ngrok, then set SERVER_BASE_URL accordingly. In oidc mode that callback is not a loopback URL, so it also has to be allowlisted: OIDC_ALLOWED_REDIRECT_URIS=https://claude.ai/api/mcp/auth_callback.
Add the public URL as a remote MCP server in Settings > Connectors (e.g. https://mcp.example.com/mcp). Claude Desktop handles the OAuth flow automatically.
OAuth via an external OIDC provider (no Dex)
For the ArgoCD instances configured with oidc.config rather than dex.config: ArgoCD talks to the identity provider directly and runs no Dex, so /api/dex/* answers nothing and AUTH_MODE=oauth cannot complete a flow. AUTH_MODE=oidc proxies to that same provider instead, and per-user RBAC works exactly as in Dex mode.
The two settings are mutually exclusive in ArgoCD (oidc.config wins and Dex is never served), so pick the mode that matches your instance:
| ArgoCD has | Mode | Provider |
|---|---|---|
dex.config |
oauth |
ArgoCD’s bundled Dex |
oidc.config |
oidc |
your IdP, directly |
docker run -p 8080:8080 \
-e ARGOCD_BASE_URL=https://argocd.example.com \
-e MCP_TRANSPORT=http \
-e AUTH_MODE=oidc \
-e SERVER_BASE_URL=http://localhost:8080 \
ghcr.io/matthisholleville/argocd-mcp:latest
No issuer or client id is needed: the server reads ArgoCD’s own /api/v1/settings at startup and uses the provider it declares, preferring cliClientID over clientID when both are set. Override with OIDC_ISSUER + OIDC_CLIENT_ID (both together) to point at something else. Client connection is identical to Dex mode.
Semantic Search (optional)
Enable Ollama-powered vector search for better results on natural language queries:
docker compose up --build -d # Starts Ollama + argocd-mcp with embeddings
Set EMBEDDINGS_ENABLED=true, OLLAMA_URL, and EMBEDDINGS_MODEL (defaults to nomic-embed-text).
Read-Only Mode (optional)
Set DISABLE_WRITE=true to prevent any disruptive action on your cluster. When enabled:
- Write endpoints are hidden —
POST,PUT,PATCH,DELETEoperations are filtered out from the search index, so the LLM never discovers them. - Write execution is blocked — even if a caller manually crafts an
execute_operationrequest with a write method, it is rejected. - Read operations work normally —
GET,HEAD,OPTIONSare unaffected.
This is ideal for production environments, demos, or any setup where you want LLMs to observe but never modify your ArgoCD resources.
# Claude Code
claude mcp add argocd -s user -- \
docker run --rm -i \
-e ARGOCD_BASE_URL=https://argocd.example.com \
-e ARGOCD_TOKEN=your-token \
-e DISABLE_WRITE=true \
ghcr.io/matthisholleville/argocd-mcp:latest
Resource Scoping (optional)
Set ALLOWED_RESOURCES to restrict which ArgoCD resource types the LLM can discover and call. This filters both search results and blocks execution of out-of-scope endpoints.
# Only expose application and version endpoints
ALLOWED_RESOURCES=ApplicationService,VersionService
Composes with DISABLE_WRITE:
# Read-only access to applications only
DISABLE_WRITE=true
ALLOWED_RESOURCES=ApplicationService
Available resource tags (from ArgoCD’s OpenAPI spec):
| Tag | Endpoints |
|---|---|
AccountService |
6 |
ApplicationService |
31 |
ApplicationSetService |
6 |
CertificateService |
3 |
ClusterService |
7 |
GPGKeyService |
4 |
NotificationService |
3 |
ProjectService |
12 |
RepoCredsService |
8 |
RepositoryService |
17 |
SessionService |
3 |
SettingsService |
2 |
VersionService |
1 |
Matching is case-insensitive (applicationservice works).
Generated Tools Mode (optional)
Set TOOL_MODE=generated to create one MCP tool per ArgoCD endpoint at startup. Instead of searching then executing, the LLM calls typed tools directly:
# Claude Code
claude mcp add argocd -s user -- \
docker run --rm -i \
-e ARGOCD_BASE_URL=https://argocd.example.com \
-e ARGOCD_TOKEN=your-token \
-e TOOL_MODE=generated \
ghcr.io/matthisholleville/argocd-mcp:latest
Rate Limiting (optional)
Protect ArgoCD from excessive API calls by setting RATE_LIMIT. Only execute_operation is rate limited — search is local and not affected.
RATE_LIMIT=10 # 10 requests/sec per user
RATE_LIMIT_BURST=20 # allow short bursts up to 20
Prompt Templates
Pre-packaged workflows for common ArgoCD operations. MCP clients (Claude Desktop, Cursor) show these as selectable prompts in their UI.
| Prompt | Description | Arguments |
|---|---|---|
unhealthy-apps |
Find all apps with degraded health or out-of-sync status | — |
sync-status |
Dashboard-style overview of all apps | — |
app-diff |
Show what would change on sync | appName (required) |
rollback |
Show history and rollback to a previous revision | appName (required) |
app-logs |
Fetch and analyze container logs | appName (required), container (optional) |
Each prompt guides the LLM through a step-by-step workflow using search_operations and execute_operation. No additional tools are needed.
Audit Logging
Audit logging is enabled by default. Every search_operations and execute_operation call emits a structured JSON log entry to stderr:
{"time":"2026-03-22T10:00:00Z","level":"INFO","msg":"audit","tool":"execute_operation","method":"GET","path":"/api/v1/applications","blocked":false,"duration_ms":142,"status_code":200,"user":"[email protected]"}
Each entry includes:
- tool —
search_operationsorexecute_operation - user — email from the OAuth token (empty in static token mode)
- method / path — the ArgoCD API call (execute) or query (search)
- status_code — upstream HTTP response code
- blocked —
trueif the call was rejected byDISABLE_WRITEorALLOWED_RESOURCES - duration_ms — round-trip time in milliseconds
- error — error message (logged at ERROR level when present)
Set AUDIT_LOG=false to disable.
Metrics
Prometheus metrics are exposed at GET /metrics whenever MCP_TRANSPORT=http (there is no HTTP surface in stdio mode, so nothing to scrape there). Unlike audit logging, metrics are always on and cannot be disabled: they carry no user identity, only a bounded tool/status label set, so there’s no volume or privacy reason to turn them off.
| Metric | Type | Labels | Description |
|---|---|---|---|
mcp_tool_calls_total |
Counter | tool, status |
Total tool calls. status is ok, blocked (rejected by DISABLE_WRITE, ALLOWED_RESOURCES, or RATE_LIMIT), or error (a failed call, or one rejected during request validation, e.g. a missing required parameter) |
mcp_tool_duration_seconds |
Histogram | tool |
Latency of completed tool calls (the round trip to ArgoCD). blocked and validation-rejected calls never reach ArgoCD and are intentionally excluded — they’d otherwise spike the histogram toward 0 and skew p95/p99 away from real latency, worst of all under heavy rate limiting. Bucketed up to 120s so slow operations (sync, rollout) don’t collapse into +Inf |
tool is search_operations / execute_operation in the default search mode, or the generated tool name (e.g. argocd_application_sync) in TOOL_MODE=generated.
curl http://localhost:8080/metrics | grep mcp_tool_
Configuration
| Variable | Required | Default | Description |
|---|---|---|---|
ARGOCD_BASE_URL |
Yes | ArgoCD server URL | |
ARGOCD_TOKEN |
When AUTH_MODE=token |
ArgoCD API token | |
AUTH_MODE |
No | token |
token (static), oauth (ArgoCD’s Dex) or oidc (external provider, no Dex) |
DEX_CLIENT_ID |
When AUTH_MODE=oauth |
argo-cd-cli |
Dex client ID |
SERVER_BASE_URL |
When AUTH_MODE=oauth, or oidc with OIDC_PROXY_CALLBACK |
http://localhost:8080 in oauth mode, none in oidc |
Public URL of this server. In oidc proxy-callback mode it is the redirect URI registered at the provider, so it has no default and startup fails without it |
OIDC_ISSUER |
No | read from ArgoCD | Provider issuer URL. Set together with OIDC_CLIENT_ID |
OIDC_CLIENT_ID |
No | read from ArgoCD | Client ID. Must be an audience ArgoCD accepts |
OIDC_CLIENT_SECRET |
For confidential clients | Added server-side on the token exchange | |
OIDC_SCOPES |
No | ArgoCD’s requestedScopes, else openid profile email groups |
Space-separated scopes. Must include openid |
OIDC_PROXY_CALLBACK |
No | true |
Register {SERVER_BASE_URL}/oauth/callback at the provider and relay codes to clients, instead of registering each client callback |
OIDC_REQUESTED_ID_TOKEN_CLAIMS |
No | JSON claims request parameter, e.g. {"id_token":{"groups":{"essential":true}}} |
|
OIDC_ALLOWED_REDIRECT_URIS |
No | Comma-separated non-loopback client redirect_uri values to accept. Loopback is always accepted; anything else is rejected |
|
OIDC_STATE_KEY |
With >1 replica | Shared secret (min 32 chars) the callback state is signed with. Unset generates a per-process key, which only works with a single replica | |
ARGOCD_SPEC_URL |
No | {base}/swagger.json |
Override spec URL |
MCP_TRANSPORT |
No | stdio |
stdio or http |
MCP_ADDR |
No | :8080 |
HTTP listen address |
ARGOCD_TLS_INSECURE |
No | false |
Skip TLS certificate verification (set true for self-signed certs) |
TOOL_MODE |
No | search |
search (2 meta-tools) or generated (1 tool per endpoint) |
DISABLE_WRITE |
No | false |
Block all write operations (POST, PUT, PATCH, DELETE) |
ALLOWED_RESOURCES |
No | Comma-separated list of resource tags to expose (e.g. ApplicationService,VersionService) |
|
RATE_LIMIT |
No | 0 (disabled) |
Max execute_operation requests per second per user |
RATE_LIMIT_BURST |
No | same as RATE_LIMIT |
Max burst size before throttling |
AUDIT_LOG |
No | true |
Structured JSON audit log for every tool call |
EMBEDDINGS_ENABLED |
No | false |
Enable Ollama vector search |
OLLAMA_URL |
No | http://localhost:11434/api |
Ollama API URL |
EMBEDDINGS_MODEL |
No | nomic-embed-text |
Ollama embedding model |
Build from source
make build
ARGOCD_BASE_URL=https://argocd.example.com ARGOCD_TOKEN=xxx ./bin/argocd-mcp
License
MIT
Install
docker run --rm -i -e ARGOCD_BASE_URL=https://argocd.example.com -e ARGOCD_TOKEN=your-token ghcr.io/matthisholleville/argocd-mcp:latestConfiguration
{
"mcpServers": {
"argocd": {
"command": "docker",
"args": ["run", "--rm", "-i",
"-e", "ARGOCD_BASE_URL=https://argocd.example.com",
"-e", "ARGOCD_TOKEN=your-token",
"ghcr.io/matthisholleville/argocd-mcp:latest"
]
}
}
}