trackmcp
Back to directory
matthisholleville

argocd-mcp

View on GitHub

MCP 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. Two meta-tools (search + execute), ~200 tokens of system prompt. Built-in OAuth via ArgoCD Dex for per-user RBAC. New ArgoCD version? Just restart.

11 stars GoOthers Updated Aug 13, 2026

Documentation

argocd-mcp

The entire ArgoCD API, exposed to LLMs via MCP.

103+ endpoints. Zero hardcoded handlers. Two modes: search or generated tools.

•

•

•

•


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) or `generated` (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.

mermaid
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.

mermaid
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?

SearchGenerated
Tools registered2103+
LLM round-trips2 (search → execute)1 (direct call)
Parameter typingRaw JSON stringsTyped individual params
Context usageLow (~200 tokens)Higher (mitigated by client deferred loading)
Best forLightweight clients, constrained contextClaude 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)

bash
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

bash
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`):

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

bash
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

bash
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.

Required: ArgoCD Dex configuration

The `argo-cd-cli` Dex client needs the callback URLs for your MCP clients registered as redirect URIs. Add a `staticClients` override in your ArgoCD `dex.config`:

yaml
staticClients:
  - id: argo-cd-cli
    name: Argo CD CLI
    public: true
    redirectURIs:
      - http://localhost
      - http://localhost:8085/auth/callback
      - http://localhost:9382/callback
      - https://claude.ai/api/mcp/auth_callback
Redirect URIUsed by
`http://localhost`ArgoCD CLI (`argocd login --sso`)
`http://localhost:8085/auth/callback`ArgoCD CLI (legacy)
`http://localhost:9382/callback`Claude Code (`--callback-port 9382`)
`https://claude.ai/api/mcp/auth_callback`Claude Desktop

ArgoCD auto-registers `argo-cd-cli` at startup and prepends it to the client list. Dex uses the last definition when there are duplicate IDs, so our override wins safely (ref).

> Note: The `argo-cd-cli` client is public (no secret), so this override is safe — unlike overriding `argo-cd` which has an internal secret (ref).

How it works under the hood:

  • The MCP server acts as an OAuth proxy to ArgoCD's Dex
  • Uses the `argo-cd-cli` public client (no secret needed)
  • The Dex `id_token` (with `aud: argo-cd-cli`) is swapped into the `access_token` field and forwarded as Bearer to ArgoCD
  • ArgoCD validates the token against Dex's JWKS and applies per-user RBAC
  • Each user only sees the applications and resources they have access to

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 hasModeProvider
`dex.config``oauth`ArgoCD's bundled Dex
`oidc.config``oidc`your IdP, directly
bash
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.

Required: identity provider configuration

The client id must be an audience ArgoCD accepts. ArgoCD validates the `aud` of the incoming `id_token` against its `oidc.config` `clientID` / `cliClientID`. A token minted for any other client is rejected with `failed to verify the token`, which is why the server defaults to the client ArgoCD itself advertises.

Register one redirect URI on that application:

code
{SERVER_BASE_URL}/oauth/callback

MCP clients bind a loopback port that cannot be pre-registered. Dex accepts any loopback redirect for a public client (RFC 8252), which is why `oauth` mode needs no such handling, but most providers require an exact match and would reject it.

So in `oidc` mode this server is the registered redirect target: it carries the client's `redirect_uri` and `state` through the upstream `state` parameter (HMAC-signed, so neither can be tampered with in the browser), then relays the code back to whichever loopback the client bound. Client callbacks need no registration at all.

Because the provider now only ever sees this server's callback, its own redirect-URI allowlist no longer constrains where an authorization code can end up, and this server has to do that itself. It accepts loopback redirects (RFC 8252, what MCP clients bind) and rejects everything else; a client that calls back to a fixed public URL, e.g. `https://claude.ai/api/mcp/auth_callback` for the hosted claude.ai integration, has to be listed in `OIDC_ALLOWED_REDIRECT_URIS`. Signing alone would not be enough: the signature proves this server minted the state, not that the target is safe.

Set `OIDC_PROXY_CALLBACK=false` to pass the client's `redirect_uri` straight through instead, for a provider that tolerates loopback redirects. Each client callback then has to be registered.

Confidential clients: if the application ArgoCD points at requires a client secret (most web-application types do), pass it as `OIDC_CLIENT_SECRET`. It is added server-side on the token exchange, so MCP clients still register as public and never see it. Public/PKCE clients (e.g. an application registered specifically for CLI use and referenced by ArgoCD as `cliClientID`) need no secret.

Groups for RBAC: ArgoCD requests the groups claim through its own `requestedIDTokenClaims`. Providers that only emit a claim on request need the same from this server, otherwise every user lands on `policy.default`:

bash
-e OIDC_REQUESTED_ID_TOKEN_CLAIMS='{"id_token":{"groups":{"essential":true}}}'

Scopes are taken from ArgoCD's own `requestedScopes` when they are discovered, since providers disagree on which scopes even exist (neither Entra ID nor Google Workspace has `groups`). They fall back to `openid profile email groups` when ArgoCD declares none, and `OIDC_SCOPES` overrides both. `openid` is required either way: without it the provider mints no `id_token`, so startup rejects a scope set that omits it and the token exchange fails with `502` rather than handing the client an opaque token that would 401 on every ArgoCD call.

Note that no `offline_access` is requested, so there is no refresh token and clients re-authenticate when the `id_token` expires. Add it through `OIDC_SCOPES` for a provider that accepts it.

How it works under the hood:

  • The MCP server acts as an OAuth proxy to the provider, discovered via `{issuer}/.well-known/openid-configuration`, whose `issuer` must match the one requested
  • `/oauth/callback` is the provider's redirect target, and relays the code to the client's own callback, which is validated against loopback plus `OIDC_ALLOWED_REDIRECT_URIS`
  • The provider's `id_token` is swapped into the `access_token` field and forwarded as Bearer to ArgoCD
  • ArgoCD validates it against the provider's JWKS and applies per-user RBAC
  • Startup fails loudly when ArgoCD advertises a Dex configuration instead, pointing at `AUTH_MODE=oauth`
  • The callback state is signed with a per-process key unless `OIDC_STATE_KEY` is set, so running more than one replica requires that shared key

Semantic Search (optional)

Enable Ollama-powered vector search for better results on natural language queries:

bash
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`, `DELETE` operations 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_operation` request with a write method, it is rejected.
  • Read operations work normally — `GET`, `HEAD`, `OPTIONS` are unaffected.

This is ideal for production environments, demos, or any setup where you want LLMs to observe but never modify your ArgoCD resources.

bash
# 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.

bash
# Only expose application and version endpoints
ALLOWED_RESOURCES=ApplicationService,VersionService

Composes with `DISABLE_WRITE`:

bash
# Read-only access to applications only
DISABLE_WRITE=true
ALLOWED_RESOURCES=ApplicationService

Available resource tags (from ArgoCD's OpenAPI spec):

TagEndpoints
`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:

bash
# 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

How generated tools work

Each endpoint's `operationId` is converted to a snake_case tool name with `argocd_` prefix:

operationIdTool name
`ApplicationService_Sync``argocd_application_sync`
`ClusterService_Get``argocd_cluster_get`
`ApplicationSetService_List``argocd_application_set_list`

Parameters are typed individually — no raw JSON needed for common cases:

code
argocd_application_sync(
  name:      "frontend"      ← path param (required)
  revision:  "HEAD"          ← body param, flattened
  dryRun:    true            ← body param, flattened
  strategy:  '{"apply":{}}'  ← nested object stays JSON string
)

Tools are annotated with MCP hints (`readOnlyHint`, `destructiveHint`, `idempotentHint`) so clients like Claude Desktop categorize them correctly (read vs write/delete).

`DISABLE_WRITE` and `ALLOWED_RESOURCES` are enforced at startup — forbidden tools are simply not generated. The LLM cannot even see them.


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.

bash
RATE_LIMIT=10              # 10 requests/sec per user
RATE_LIMIT_BURST=20        # allow short bursts up to 20

How rate limiting works

Rate limiting uses a token bucket per user. Each user gets a bucket that refills at `RATE_LIMIT` tokens per second, with a maximum of `RATE_LIMIT_BURST` tokens. When the bucket is empty, requests are rejected until tokens refill.

Auth modeBucket keyBehavior
OAuthUser email from JWTEach user has an independent limit
Static tokenShared `"static-token"` keyAll clients share one bucket

> Note: In static token mode, an aggressive LLM can starve other clients. Prefer OAuth mode in multi-user production setups.

When a request is rate limited:

  • The call never reaches ArgoCD — rejected before the proxy
  • An audit log entry is emitted with `blocked: true`
  • The LLM receives a clear error: `"rate limit exceeded: too many requests, please slow down"`

If `RATE_LIMIT_BURST` is not set, it defaults to the `RATE_LIMIT` value. Set `RATE_LIMIT=0` (or omit it) to disable rate limiting entirely.


Prompt Templates

Pre-packaged workflows for common ArgoCD operations. MCP clients (Claude Desktop, Cursor) show these as selectable prompts in their UI.

PromptDescriptionArguments
`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:

json
{"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":"alice@example.com"}

Each entry includes:

  • tool — `search_operations` or `execute_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 — `true` if the call was rejected by `DISABLE_WRITE` or `ALLOWED_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.

MetricTypeLabelsDescription
`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`.

bash
curl http://localhost:8080/metrics | grep mcp_tool_

Example PromQL

promql
# Call rate by tool, last 5 minutes
sum by (tool) (rate(mcp_tool_calls_total[5m]))

# p95 latency
histogram_quantile(0.95, sum by (le, tool) (rate(mcp_tool_duration_seconds_bucket[5m])))

# Error ratio
sum(rate(mcp_tool_calls_total{status="error"}[5m])) / sum(rate(mcp_tool_calls_total[5m]))

Configuration

VariableRequiredDefaultDescription
`ARGOCD_BASE_URL`YesArgoCD 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`Noread from ArgoCDProvider issuer URL. Set together with `OIDC_CLIENT_ID`
`OIDC_CLIENT_ID`Noread from ArgoCDClient ID. Must be an audience ArgoCD accepts
`OIDC_CLIENT_SECRET`For confidential clientsAdded server-side on the token exchange
`OIDC_SCOPES`NoArgoCD'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`NoJSON `claims` request parameter, e.g. `{"id_token":{"groups":{"essential":true}}}`
`OIDC_ALLOWED_REDIRECT_URIS`NoComma-separated non-loopback client `redirect_uri` values to accept. Loopback is always accepted; anything else is rejected
`OIDC_STATE_KEY`With >1 replicaShared 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`NoComma-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`Nosame 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

bash
make build
ARGOCD_BASE_URL=https://argocd.example.com ARGOCD_TOKEN=xxx ./bin/argocd-mcp

License

MIT

Frequently asked questions

What is argocd-mcp?

argocd-mcp is MCP 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. Two meta-tools (search + execute), ~200 tokens of system prompt. Built-in OAuth via ArgoCD Dex for per-user RBAC. New ArgoCD version? Just restart.

How do I install argocd-mcp?

Open the GitHub repository and follow its README. Most MCP servers are added to your client's MCP config, then called by your agent.

Is argocd-mcp open source?

Yes — it is hosted on GitHub at https://github.com/matthisholleville/argocd-mcp and has 11 stars.

Related MCP tools

Run your own MCP server? See who uses it and what to fix.

Measure it with TrackMCP