KC

kenprole/chainguard-mcp

Security testing
1 stars 0 forks Quality 48 Trend 13

MCP server for discovering Chainguard container images

Overview

An MCP server that helps AI assistants discover, inspect and adopt Chainguard container images, plus a web UI for browsing the same data yourself. This is an unofficial project, not affiliated with Chainguard. Run the server in HTTP mode and open http://127.0.0.1:8080/: The UI is built into the binary and runs the same code as the MCP tools, through a JSON API under /api/ (which lists its endpoints). Screenshots are from 2026-10-11. Search every Chainguard image and filter to the free ones, or switch to the : one square per image, green for free, grouped by variant or name prefix. Click a group's header to zoom in. Checking every image's free-tier status takes about 30 seconds the first time, with a progress bar. - the pinned reference for your Dockerfile, the runtime user, entrypoint, platforms and download sizes. - every OS package from the image's signed SBOM, a license breakdown, and the full SPDX download.

README

chainguard-mcp

An MCP server that helps AI assistants discover, inspect and adopt Chainguard container images, plus a web UI for browsing the same data yourself.

This is an unofficial project, not affiliated with Chainguard.

Web UI

Run the server in HTTP mode and open http://127.0.0.1:8080/:

./chainguard-mcp -http 127.0.0.1:8080

The UI is built into the binary and runs the same code as the MCP tools, through a JSON API under /api/ (which lists its endpoints). Screenshots are from 2026-10-11.

Catalog

Search every Chainguard image and filter to the free ones, or switch to the Map: one square per image, green for free, grouped by variant or name prefix. Click a group’s header to zoom in. Checking every image’s free-tier status takes about 30 seconds the first time, with a progress bar.

Image page

  • Overview: the pinned reference for your Dockerfile, the runtime user, entrypoint, platforms and download sizes.
  • Packages & SBOM: every OS package from the image’s signed SBOM, a license breakdown, and the full SPDX download.
  • Security: recorded vulnerability fixes per source package, and a CVE or GHSA lookup.
  • Compare: two tags side by side, by default latest and latest-dev.

The Security tab shows Chainguard’s open advisories for the image’s packages (read through OSV.dev) above the recorded fixes. Neither is a full scan.

Find alternative

Enter an image you use today, such as node:20, to get its Chainguard replacement and migration notes, then a comparison of the two: size, build date, installed OS packages, and known vulnerabilities per OSV.dev (with Chainguard’s statuses from its advisory feed), split by whether a fix exists. Docker Hub images only; a first comparison takes about 20 seconds.

Sizes are compressed download sizes in decimal units (1 MB = 1,000,000 bytes), as Docker shows them. Each feature’s exact behavior and edge cases are specified in openspec/specs/.

Tools

Tool Description
list_images List images in the Chainguard directory, with free-tier status. Optional query, free_only, limit (default 100) and offset.
get_image_tags List the tags for cgr.dev/chainguard/.
get_image_details Platforms and download sizes, digest, creation time, user, entrypoint, command, working directory and environment.
pin_image Resolve a tag to its digest, e.g. cgr.dev/chainguard/python:latest@sha256:…, for reproducible builds.
get_image_packages OS packages from the image’s signed SBOM, with versions, licenses and source packages, plus whether it has a shell or apk.
save_sbom Save the image’s full SPDX SBOM as a JSON file, for compliance or scanning tools. See Saving SBOMs.
check_vulnerabilities Recorded fixes per package (Wolfi security database) plus the known open advisories Chainguard has published for the image’s packages (via OSV.dev), counted by vulnerability with a status each. Pass a CVE/GHSA/CGA ID to look it up in both.
find_alternative Suggest the Chainguard image to replace an upstream one, e.g. node:20-alpine → cgr.dev/chainguard/node.
compare_with_upstream Compare a Docker Hub image as you run it, e.g. node:20, with its Chainguard replacement by size, build date, OS packages and known vulnerabilities (from OSV.dev, with Chainguard’s statuses from its advisory feed), plus an end-of-life note. See Upstream comparison.

It also provides a migrate_dockerfile prompt that walks the assistant through converting a Dockerfile to Chainguard images using the tools above.

Every tool except list_images, find_alternative and the upstream side of compare_with_upstream needs anonymous Chainguard registry access, so it only works for free-tier images. Paid images return a clear error.

Example questions

Once the server is connected, ask Claude questions like these. Claude picks the right tools, and often chains several together.

A quick demo

Each question builds on the one before:

  1. “What free Chainguard images are there for Python?”

  2. “Tell me about the Chainguard python image. Does it run as root? What architectures does it support?”

  3. “Does the python image have a shell? What about latest-dev?”

  4. “Is the python image affected by CVE-2022-3602?”

  5. “I’m using node:20-alpine. What’s the Chainguard equivalent?”

  6. Paste a Dockerfile and ask Claude to migrate it:

    Migrate this Dockerfile to Chainguard images and pin them by digest:
    
    ```dockerfile
    FROM python:3.12-slim
    WORKDIR /app
    COPY requirements.txt .
    RUN pip install -r requirements.txt
    COPY . .
    CMD ["python", "app.py"]
    ```
    

Claude Code also turns the built-in migration prompt into a slash command: type /mcp__chainguard__migrate_dockerfile followed by your Dockerfile.

Discovery

  • “How many Chainguard images are there in total?”
  • “List all the FIPS images for Java.”
  • “Which Chainguard images exist for PostgreSQL?”

Details and pinning

  • “How big is the Chainguard static image to download?”
  • “What environment variables does cgr.dev/chainguard/node set?”
  • “Give me a digest-pinned FROM line for the Chainguard go image.”

Packages and SBOMs

  • “Show me the SBOM for the Chainguard python image.”
  • “Give me the SBOM for the arm64 version of the Chainguard node image as a table.”
  • “Save the full SBOM for the Chainguard python image.”
  • “What version of OpenSSL is in the Chainguard python image?”
  • “List the licenses of every package in the Chainguard node image.”
  • “Compare the packages in python:latest and python:latest-dev.”

Vulnerabilities

  • “Summarize the security fixes recorded for the Chainguard python image.”
  • “Are there any fixes the python image is missing?”
  • “Is the Chainguard node image affected by CVE-2024-12797?”

Alternatives

  • “What should I use instead of gcr.io/distroless/static-debian12?”
  • “We use eclipse-temurin:21-jre and mcr.microsoft.com/dotnet/aspnet:8.0. What are the Chainguard replacements, and are they free?”
  • “Replace ubuntu:24.04 with a Chainguard base image.”
  • “We run node:20 today. How would switching to Chainguard change its size, packages and known vulnerabilities?”

Data sources

Data Source
Image list The public directory sitemap (images.chainguard.dev/sitemap.xml), cached for 1 hour. The cgr.dev catalog API isn’t available.
Free-tier status Whether cgr.dev issues an anonymous pull token, cached for 24 hours.
Tags, details, digests The cgr.dev registry API, with an anonymous token.
Packages The SPDX SBOM that Chainguard attaches to each image as a signed attestation (sha256-.att).
Recorded fixes The Wolfi security database, cached for 6 hours.
Open advisories Chainguard’s published advisories for the image’s packages, found through OSV.dev (ecosystems Wolfi and Chainguard, one lookup per package), then each advisory’s current record read from Chainguard’s own advisory feed (advisories.cgr.dev), because OSV.dev can serve older copies. Both cached for 1 hour.
Upstream images Docker Hub’s registry with an anonymous token: the image’s SPDX SBOM attestation, size and build date. Docker Hub allows about 100 anonymous requests an hour per IP; a comparison makes about 5.
Known vulnerabilities (comparison) OSV.dev, queried per package version, cached for 24 hours (1 hour for Chainguard packages). The Chainguard side’s advisories are then read from Chainguard’s advisory feed, as for open advisories.
End of life (comparison) endoflife.date, cached for 24 hours.

The Wolfi database records which vulnerabilities each package version fixes, and which never applied. It doesn’t list unfixed ones; those come from Chainguard’s open advisories, found through OSV.dev with their status read from Chainguard’s advisory feed, and shown separately (advisories already fixed in the installed version are left out; a vulnerability is counted once however many advisories and packages it has, and “not yet assessed” means Chainguard detected it but hasn’t decided whether it applies). Neither is a full scan, so check_vulnerabilities isn’t a replacement for a scanner such as grype, and an empty result never means an image has no vulnerabilities.

Upstream comparison

compare_with_upstream and the Alternatives page’s comparison (GET /api/compare-upstream, a background job the UI polls) read the upstream image’s SBOM from Docker Hub and look up each package on OSV.dev. For the Chainguard image, each advisory found is read again from Chainguard’s advisory feed, as on the Security tab, and those already fixed in the installed version aren’t counted:

  • Binary vs source packages: a Debian SBOM lists installed binary packages and the source packages they were built from. The OS package count is the binary packages; vulnerabilities are looked up by distinct source package, which is how Debian keys its advisories.
  • Counting: each vulnerability counts once per image however many packages it affects, and if it is fixable for any package it counts as fix available. Debian’s “unimportant” and “end-of-life” urgencies and Chainguard’s “fix not planned” count as won’t fix; a Chainguard advisory pending an upstream fix or not yet assessed counts as no fix yet. A fix released only for another Debian release doesn’t count.
  • Accuracy: on 2026-10-11 the comparison found all 817 CVEs that grype reported for node:20’s OS packages. CHAINGUARD_LIVE=1 go test -run TestLive/upstream repeats that check when grype is on PATH.
  • Not covered: registries other than Docker Hub, language packages, severity ratings, and images without an SBOM attestation.

Catalog history

Chainguard’s endpoints only show the catalog as it is today, so a scheduled GitHub Action (history.yml) records it daily. Each day at about 06:17 UTC it appends one line to catalog-history.jsonl on the data branch (not main, whose rules require pull requests): the date, how many images there are and how many are free, and which images were added, retired, or moved between free and subscription since the last line. The first 17 monthly lines (April 2024 to October 2026) were rebuilt from the Internet Archive’s copies of the sitemap and have image names only. Free-tier status starts with the first live snapshot, so nothing is known about it before then. The file format is described in the data branch’s README.

The binary does this itself, with two flags that run and exit without starting the server:

go run . -record-history catalog-history.jsonl     # take today's snapshot (once per UTC day; about 30 seconds)
go run . -backfill-history catalog-history.jsonl   # one-time: build an empty file from the Internet Archive

A snapshot is refused, and the job fails, if the sitemap is empty or would remove more than 5% of the previous snapshot’s images. Nothing in the server or UI reads the history yet.

Install

Download a binary for your platform from Releases, or build from source:

npm --prefix web ci
npm --prefix web run build   # optional: builds the web UI, which the Go binary embeds
go build -o chainguard-mcp .

Without the UI build, everything else still works and / shows how to build it.

Run

./chainguard-mcp                     # stdio (for Claude Desktop / Claude Code)
./chainguard-mcp -http 127.0.0.1:8080  # web UI at /, MCP at /mcp, JSON API under /api/ (GET /api/ lists the endpoints), health check at /healthz
./chainguard-mcp -version

Add it to Claude Code:

claude mcp add -s user chainguard -- /path/to/chainguard-mcp

JSON API

With -http, the API that the web UI uses is served under /api/. Every endpoint answers GET with JSON (the SBOM as an application/spdx+json download), and GET /api/ lists them with their parameters:

curl http://127.0.0.1:8080/api/
Endpoint Parameters Example
/api/images query, free_only=true, limit (default 100, max 1000), offset /api/images?query=python&free_only=true
/api/groups group_by (variant or prefix), query /api/groups?group_by=prefix&query=nginx
/api/families query /api/families?query=nginx
/api/images/{name}/tags /api/images/python/tags
/api/images/{name}/details tag (default latest) /api/images/python/details?tag=latest
/api/images/{name}/pin tag /api/images/python/pin?tag=latest-dev
/api/images/{name}/packages tag, arch (amd64 or arm64), query /api/images/python/packages?query=ssl
/api/images/{name}/vulnerabilities tag, id (a CVE or GHSA ID) /api/images/python/vulnerabilities?id=CVE-2024-12797
/api/images/{name}/advisories tag, id (a CVE, GHSA or CGA ID) /api/images/python/advisories?id=CVE-2026-89092
/api/images/{name}/sbom tag, arch /api/images/python/sbom?tag=latest
/api/alternatives image (required) /api/alternatives?image=node:20-alpine
/api/compare-upstream image (required, a Docker Hub image as you run it), arch /api/compare-upstream?image=node:20

Errors are JSON {"error": "...", "code": "..."} with status 400 invalid_input or unsupported_registry (only Docker Hub images can be compared), 403 not_public (subscription-only image), 404 not_found (also for unknown /api/ paths) or no_alternative, 429 rate_limited (Docker Hub’s anonymous limit), or 502 upstream_error or source_failed (with the failed source). The API has no authentication; bind it to 127.0.0.1 unless you mean to share it.

Saving SBOMs

save_sbom writes files named like python-latest-amd64.spdx.json, and never overwrites an existing file unless asked to. Where they go depends on how the server runs:

  • Stdio mode: the folder the server was started in. For Claude Code, that’s your project folder.
  • HTTP mode: saving is turned off, because remote clients would be writing files to the server’s machine.

Use -sbom-dir to choose a folder in either mode:

claude mcp add -s user chainguard -- /path/to/chainguard-mcp -sbom-dir /path/to/sboms

Docker

The image is built on Chainguard’s node and go images (build) and static image (runtime), and runs as a non-root user. It includes the web UI.

docker build -t chainguard-mcp .

HTTP mode with the web UI (the default). Bind the host port to 127.0.0.1 so it isn’t reachable from your network, then open http://127.0.0.1:8080/:

docker run -d --rm -p 127.0.0.1:8080:8080 chainguard-mcp

Stdio mode. Passing an empty -http= disables HTTP:

claude mcp add chainguard -- docker run -i --rm chainguard-mcp -http=

To save SBOMs from the container, mount a folder and point -sbom-dir at it:

docker run -i --rm -v "$PWD/sboms:/sboms" chainguard-mcp -http= -sbom-dir /sboms

On Linux, the folder must be writable by the container’s user (UID 65532).

Multi-arch build (cross-compiles, no emulation needed):

docker buildx build --platform linux/amd64,linux/arm64 -t chainguard-mcp .

Develop and test

go test ./...

The tests run offline against a fake Chainguard registry (fake_test.go). To also run the end-to-end test against the live Chainguard endpoints:

CHAINGUARD_LIVE=1 go test -run TestLive -v ./...

The web UI is a React + TypeScript app in web/, built with Vite. It needs Node.js 24:

cd web
npm ci
npm run lint && npm run typecheck && npm test

For live reloading while working on the UI, run the Go server and the Vite dev server together, then open http://localhost:5173/. Vite forwards /api requests to the Go server.

go run . -http 127.0.0.1:8080
npm --prefix web run dev

Releasing

CI (.github/workflows/ci.yml) runs formatting, vet and tests on Linux and Windows, lints, typechecks, tests and builds the web UI, builds the Docker image and checks the UI loads, and dry-runs the release on every push to main.

To publish a release, push a version tag:

git tag v0.2.0
git push origin v0.2.0

.github/workflows/release.yml then uses GoReleaser to build the web UI and binaries for Linux, macOS and Windows (amd64 and arm64) and attach them to a GitHub Release with checksums.

Project layout

File Contents
main.go Flags and transport setup (stdio or HTTP).
service.go Shared logic behind both the MCP tools and the web API.
tools.go MCP tool and prompt definitions.
web.go JSON API under /api/, security headers, and serving the UI.
webui.go Embeds the built UI from web/dist.
catalog.go Image list from the directory sitemap.
registry.go Anonymous cgr.dev client: tokens, free-tier checks, tags, manifests, blobs.
details.go get_image_details and pin_image.
sbom.go SBOM attestation parsing for get_image_packages, and saving for save_sbom.
vulns.go Wolfi security database and APK version comparison.
advisories.go, advisory_feed.go Open advisories for an image: grouping OSV.dev’s Chainguard records by vulnerability, their statuses and totals, and the client for Chainguard’s advisory feed that supplies each advisory’s current record.
alternatives.go Upstream image → Chainguard image mapping, and grouping images into families of variants.
groups.go Grouping images by variant kind or name prefix for the catalog map.
compare.go, compare_job.go The upstream comparison and the background job behind its API.
osv.go, endoflife.go, upstream.go, upstream_packages.go OSV.dev and endoflife.date clients, Docker Hub reference parsing, and the SBOM package lists for Debian, Ubuntu and Alpine images.
history.go Catalog history: the daily snapshot (-record-history) and the Internet Archive backfill (-backfill-history).
web/ React + TypeScript web UI.

License

MIT. See LICENSE.

View this README on GitHub

Install

docker run -d --rm -p 127.0.0.1:8080:8080 chainguard-mcp