Sign inSign up

anchore/enterprise-mcp

By anchore

Updated about 1 hour ago

MCP Server for Anchore Enterprise v5.27+

Image
Security
Integration & delivery
Machine learning & AI
0

2.2K

anchore/enterprise-mcp repository overview

Anchore Enterprise MCP Server

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.

  • Requires: Anchore Enterprise 5.27 or later
  • Image: anchore/enterprise-mcp — runs as uid 65532, no shell, no package manager

Quick start

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

Which transport?

Your setupTransportSee
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 podhttpDocker 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.


Configuration

All configuration is by environment variable.

Required
VariableDescription
ANCHORE_MCP_API_KEYAnchore Enterprise API key (the only supported credential)
ANCHORE_MCP_ENDPOINTAnchore Enterprise URL, without a version path (e.g. https://anchore.example.com:8228)
Optional
VariableDescriptionDefault
ANCHORE_MCP_INSECUREPermit a plain http:// endpoint and skip TLS verificationfalse
ANCHORE_MCP_CA_CERTPath (inside the container) to a custom CA certificatesystem trust store
ANCHORE_MCP_TIMEOUTUpstream request timeout, seconds60
ANCHORE_MCP_LOG_LEVELdebug, info, warn, errorinfo
ANCHORE_MCP_AUDIT_LOGFile path for the JSON audit logstderr
ANCHORE_MCP_TRANSPORTstdio or httpstdio

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.

HTTP transport

Only read when ANCHORE_MCP_TRANSPORT=http.

VariableDescriptionDefault
ANCHORE_MCP_HTTP_AUTH_TOKENBearer token required on every request. Minimum 32 characters — the server refuses to start without a valid one.required
ANCHORE_MCP_HTTP_BINDBind address127.0.0.1
ANCHORE_MCP_HTTP_PORTListen port8080
ANCHORE_MCP_HTTP_ALLOW_NON_LOOPBACKOpt-in required to bind a non-loopback addressfalse
ANCHORE_MCP_HTTP_ALLOWED_HOSTSComma-separated host:port allowlist (DNS-rebinding protection). Required when binding non-loopbackauto for loopback
ANCHORE_MCP_HTTP_ALLOWED_ORIGINSComma-separated Origin allowlistauto for loopback
ANCHORE_MCP_HTTP_TRUSTED_PROXIESComma-separated IPs/CIDRs trusted to set X-Forwarded-For. Empty ignores the header entirelyempty
ANCHORE_MCP_HTTP_SESSION_MODEstateful or statelessstateful
ANCHORE_MCP_HTTP_PATHMount path of the MCP endpoint/mcp
ANCHORE_MCP_HTTP_HEALTHZ_PATHUnauthenticated 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 on the host, MCP in a container

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"
]
Alternative: a long-running container on the HTTP transport

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.


Docker Compose: agent in an adjacent service

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").


Operating the image

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.


Tools

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.


Support

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.

Tag summary

Content type

Image

Digest

sha256:ec89ab52c

Size

58.9 MB

Last updated

about 1 month ago

docker pull anchore/enterprise-mcp