MCP Server for Anchore Enterprise v5.27+
2.2K
A Model Context Protocol server that exposes the Anchore Enterprise API to LLM agents — images, vulnerabilities, policies, SBOMs, events, system health, and the reporting/GraphQL API — as MCP tools.
Every call runs as the single Anchore user you configure, subject to that user's permissions. There is no per-request identity: this is a single-tenant server.
anchore/enterprise-mcp — runs as uid 65532, no shell, no package managerdocker run -i --rm \
-e ANCHORE_MCP_API_KEY='<api-key>' \
-e ANCHORE_MCP_ENDPOINT=https://anchore.example.com:8228 \
anchore/enterprise-mcp:latest
That starts the server on stdio, which expects an MCP client on the other end of the pipe — run it this way to confirm the image starts and your configuration parses, then wire it into a client using one of the configurations below.
Startup validates the form of your settings, not connectivity: the server comes up and logs your
endpoint without ever contacting Anchore. To confirm the endpoint, credentials, and backend are
actually good, connect a client and call anchore_system_status — it distinguishes a bad endpoint
from bad credentials from a degraded deployment.
An API key is the only credential this server accepts. Create one in the Anchore UI under your
user's API keys, or via POST /v2/user/api-keys. On the wire it is ordinary HTTP Basic auth with the
literal username _api_key and the key as the password — the server builds that pair itself, so
there is nothing else to configure. Prefer a key scoped to a dedicated service account: every tool
call runs under that identity.
| Your setup | Transport | See |
|---|---|---|
| Agent runs the MCP as a child process (Claude Code, Claude Desktop, most IDE clients) | stdio (default) | Claude Code on the host, MCP in a container |
| Agent is a separate container or pod | http | Docker Compose: agent in an adjacent service |
stdio is the default and needs no extra configuration. The HTTP transport is opt-in
(ANCHORE_MCP_TRANSPORT=http) and is intended for single-tenant, internal-network use only — it
does not terminate TLS. Put a sidecar, ingress, or service mesh in front of it if you need TLS
on that hop.
All configuration is by environment variable.
| Variable | Description |
|---|---|
ANCHORE_MCP_API_KEY | Anchore Enterprise API key (the only supported credential) |
ANCHORE_MCP_ENDPOINT | Anchore Enterprise URL, without a version path (e.g. https://anchore.example.com:8228) |
| Variable | Description | Default |
|---|---|---|
ANCHORE_MCP_INSECURE | Permit a plain http:// endpoint and skip TLS verification | false |
ANCHORE_MCP_CA_CERT | Path (inside the container) to a custom CA certificate | system trust store |
ANCHORE_MCP_TIMEOUT | Upstream request timeout, seconds | 60 |
ANCHORE_MCP_LOG_LEVEL | debug, info, warn, error | info |
ANCHORE_MCP_AUDIT_LOG | File path for the JSON audit log | stderr |
ANCHORE_MCP_TRANSPORT | stdio or http | stdio |
HTTPS is enforced. An http:// endpoint makes the server exit non-zero at startup unless you
set ANCHORE_MCP_INSECURE=true. This is the first thing you hit pointing the container at a local
development stack.
Only read when ANCHORE_MCP_TRANSPORT=http.
| Variable | Description | Default |
|---|---|---|
ANCHORE_MCP_HTTP_AUTH_TOKEN | Bearer token required on every request. Minimum 32 characters — the server refuses to start without a valid one. | required |
ANCHORE_MCP_HTTP_BIND | Bind address | 127.0.0.1 |
ANCHORE_MCP_HTTP_PORT | Listen port | 8080 |
ANCHORE_MCP_HTTP_ALLOW_NON_LOOPBACK | Opt-in required to bind a non-loopback address | false |
ANCHORE_MCP_HTTP_ALLOWED_HOSTS | Comma-separated host:port allowlist (DNS-rebinding protection). Required when binding non-loopback | auto for loopback |
ANCHORE_MCP_HTTP_ALLOWED_ORIGINS | Comma-separated Origin allowlist | auto for loopback |
ANCHORE_MCP_HTTP_TRUSTED_PROXIES | Comma-separated IPs/CIDRs trusted to set X-Forwarded-For. Empty ignores the header entirely | empty |
ANCHORE_MCP_HTTP_SESSION_MODE | stateful or stateless | stateful |
ANCHORE_MCP_HTTP_PATH | Mount path of the MCP endpoint | /mcp |
ANCHORE_MCP_HTTP_HEALTHZ_PATH | Unauthenticated health endpoint | /healthz |
Binding anywhere other than loopback requires both ANCHORE_MCP_HTTP_ALLOW_NON_LOOPBACK=true
and a non-empty ANCHORE_MCP_HTTP_ALLOWED_HOSTS — two separate opt-ins, so no single mistake
exposes the listener. GET /healthz is unauthenticated and returns a fixed {"status":"ok"} for
container and Kubernetes probes.
Claude Code launches the container per session and talks to it over stdio. The -i flag is
load-bearing — without it the container gets no stdin and the session dies immediately.
Write this to .mcp.json in your project root:
{
"mcpServers": {
"anchore-enterprise": {
"command": "docker",
"args": [
"run", "--rm", "-i",
"-e", "ANCHORE_MCP_API_KEY",
"-e", "ANCHORE_MCP_ENDPOINT",
"anchore/enterprise-mcp:latest"
],
"env": {
"ANCHORE_MCP_API_KEY": "<api-key>",
"ANCHORE_MCP_ENDPOINT": "https://anchore.example.com:8228"
}
}
}
}
The bare -e NAME arguments (no =value) tell docker run to forward the variable from the
environment Claude Code builds from the env block. Keep the two lists in sync: a variable in env
that isn't forwarded with -e never reaches the container.
Equivalent one-liner, if you'd rather not hand-write the file:
claude mcp add anchore-enterprise -- \
docker run --rm -i \
-e ANCHORE_MCP_API_KEY='<api-key>' \
-e ANCHORE_MCP_ENDPOINT=https://anchore.example.com:8228 \
anchore/enterprise-mcp:latest
Then claude → /mcp to confirm the server connected, and ask it something like "check Anchore
system status".
Pointing at a local Anchore stack. If Anchore runs in Docker on the same host, join its compose
network and address it by service name — plus ANCHORE_MCP_INSECURE=true, since such stacks
usually speak plain HTTP:
"args": [
"run", "--rm", "-i",
"-e", "ANCHORE_MCP_API_KEY",
"-e", "ANCHORE_MCP_ENDPOINT", "-e", "ANCHORE_MCP_INSECURE",
"--network", "anchore_default",
"anchore/enterprise-mcp:latest"
]
If you'd rather keep one container up instead of launching one per session, publish the HTTP transport to the host. Three settings are interlocked here, and getting any of them wrong produces a listener you can't talk to:
export ANCHORE_MCP_TOKEN=$(openssl rand -hex 32)
docker run -d --name anchore-mcp \
-e ANCHORE_MCP_API_KEY='<api-key>' \
-e ANCHORE_MCP_ENDPOINT=https://anchore.example.com:8228 \
-e ANCHORE_MCP_TRANSPORT=http \
-e ANCHORE_MCP_HTTP_AUTH_TOKEN="$ANCHORE_MCP_TOKEN" \
-e ANCHORE_MCP_HTTP_BIND=0.0.0.0 \
-e ANCHORE_MCP_HTTP_ALLOW_NON_LOOPBACK=true \
-e ANCHORE_MCP_HTTP_ALLOWED_HOSTS=127.0.0.1:8080 \
-p 127.0.0.1:8080:8080 \
anchore/enterprise-mcp:latest
claude mcp add --transport http anchore-enterprise http://127.0.0.1:8080/mcp \
--header "Authorization: Bearer $ANCHORE_MCP_TOKEN"
Why each piece is required:
ANCHORE_MCP_HTTP_BIND=0.0.0.0 — the default 127.0.0.1 binds the container's loopback, so
a published port maps to a listener nothing outside the container can reach. Setting it in turn
forces ANCHORE_MCP_HTTP_ALLOW_NON_LOOPBACK=true and an explicit ALLOWED_HOSTS; the server
refuses to start otherwise.ANCHORE_MCP_HTTP_ALLOWED_HOSTS must name the address and port the client dials, not the
container's internal port — that's the Host header the server checks. Remapping the port (e.g.
-p 127.0.0.1:18080:8080) means the allowlist entry is 127.0.0.1:18080. A mismatch answers
421 Misdirected Request / Invalid Host header.-p 127.0.0.1:8080:8080 keeps the published port on the host's loopback. Plain -p 8080:8080
publishes on every host interface, and the server terminates no TLS.Note that /healthz answers 200 regardless of the Host allowlist, so a passing health check does
not prove /mcp will accept your client. Verify with claude mcp list, which should report
✔ Connected.
Two containers cannot share stdio, so an agent in its own service reaches the MCP over the HTTP
transport. The MCP binds 0.0.0.0 so the agent container can connect, and publishes no ports —
nothing outside the compose network can reach it.
services:
anchore-mcp:
image: anchore/enterprise-mcp:latest
restart: unless-stopped
environment:
ANCHORE_MCP_API_KEY: ${ANCHORE_MCP_API_KEY:?set in .env}
ANCHORE_MCP_ENDPOINT: https://anchore.example.com:8228
ANCHORE_MCP_TRANSPORT: http
ANCHORE_MCP_HTTP_AUTH_TOKEN: ${ANCHORE_MCP_TOKEN:?set in .env}
ANCHORE_MCP_HTTP_BIND: 0.0.0.0
ANCHORE_MCP_HTTP_ALLOW_NON_LOOPBACK: "true"
ANCHORE_MCP_HTTP_ALLOWED_HOSTS: "anchore-mcp:8080"
ANCHORE_MCP_HTTP_ALLOWED_ORIGINS: "http://anchore-mcp:8080"
networks: [agent-net]
healthcheck:
# The image has no shell and no curl, so probe with its own interpreter.
test: ["CMD", "/app/.venv/bin/python3.12", "-c",
"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/healthz')"]
interval: 10s
timeout: 5s
retries: 3
agent:
image: your-agent:latest
depends_on:
anchore-mcp:
condition: service_healthy
environment:
# However your agent framework names these — the two facts it needs are
# the URL and the bearer token.
MCP_SERVER_URL: http://anchore-mcp:8080/mcp
MCP_SERVER_TOKEN: ${ANCHORE_MCP_TOKEN}
networks: [agent-net]
networks:
agent-net:
.env alongside the compose file:
ANCHORE_MCP_API_KEY=<api-key>
ANCHORE_MCP_TOKEN=<openssl rand -hex 32>
Generate the token with openssl rand -hex 32 (64 characters, comfortably over the 32-character
minimum). Bring it up with docker compose up -d, and check docker compose logs anchore-mcp if
the agent can't connect — a rejected request is audited there.
On network isolation: agent-net is a normal bridge network, so the MCP container has the
egress it needs to reach an external ANCHORE_MCP_ENDPOINT. Adding internal: true blocks that
egress — only do it when Anchore itself is a service on the same compose network (in which case set
the endpoint to http://api:8228 and add ANCHORE_MCP_INSECURE: "true").
Distroless, no shell. docker exec ... sh and kubectl exec -it will not get you a prompt.
There is no package manager either, so the image cannot be patched in place — pull a newer tag. To
inspect it, override the entrypoint with the interpreter:
docker run --rm --entrypoint /app/.venv/bin/python3.12 anchore/enterprise-mcp:latest \
-c 'import anchore_mcp, sys; print(sys.version)'
Runs as uid 65532. Any path the container writes to must be writable by that uid, and a mounted CA certificate must be readable by it. The default of audit-to-stderr needs no writable mount.
docker run -i --rm \
-e ANCHORE_MCP_API_KEY='<api-key>' \
-e ANCHORE_MCP_ENDPOINT=https://anchore.example.com:8228 \
-e ANCHORE_MCP_CA_CERT=/certs/ca.crt \
-e ANCHORE_MCP_AUDIT_LOG=/logs/audit.log \
-v /path/to/certs:/certs:ro \
-v /path/to/logs:/logs \
anchore/enterprise-mcp:latest
Pre-create the log directory with chown 65532 /path/to/logs, or use a matching fsGroup on
Kubernetes.
stdout is reserved for the MCP protocol. Logs and audit records go to stderr. Do not add anything that writes to stdout, and do not pipe stdout anywhere in stdio mode — it corrupts the session.
FIPS crypto policy applies to the outbound TLS connection. The base image
(registry.access.redhat.com/hi/python:3.12-fips) refuses SHA-1-signed certificates, RSA keys
under 2048 bits, and TLS below 1.2 — connections an ordinary base image would have accepted. If
your Anchore deployment sits behind an older internal CA, the certificate you pass via
ANCHORE_MCP_CA_CERT may need to be reissued. The failure surfaces as a TLS handshake error that
does not obviously name the policy as the cause.
Audit logging. Every tool invocation emits one JSON line with the tool name and category, the
parameters (keys matching password/api_key/secret/token/credential are redacted), the
upstream API path and HTTP status, duration, and response size. In HTTP mode, failed
authentications are logged separately as event_type=auth_failure with the resolved source IP; the
submitted token is never echoed.
No EXPOSE in the image — port publishing is a deployment decision, and stdio mode needs no
port at all.
28 tools, all prefixed anchore_, covering: image inventory and analysis submission; vulnerability
listing, CVE/GHSA lookup and VEX-style annotations; policy listing and evaluation; SBOM export
(native, SPDX, CycloneDX) and package content; system events; deployment health
(anchore_system_status — the one to reach for when other tools fail, since it separates a bad
endpoint from bad credentials from a degraded backend); nine reporting/GraphQL tools for
cross-image vulnerability and policy impact, including Kubernetes and ECS runtime inventory; and
anchore_api_call for any endpoint without a curated wrapper.
Image, vulnerability, and SBOM comparisons return a four-way added / removed / modified /
common partition rather than a raw diff.
Issues and questions: [email protected] or file issues in your Anchore support portal.
The license terms for this image are in /LICENSE.txt inside it.
Content type
Image
Digest
sha256:ec89ab52c…
Size
58.9 MB
Last updated
about 1 month ago
docker pull anchore/enterprise-mcp