Skip to main content
Glama
joepangallo

image-delta-mcp

image-delta-mcp — UNOFFICIAL: built as interview prep, not affiliated with or endorsed by Chainguard.

An MCP server over Chainguard's public, free-tier image data that answers, inside Claude Code:

"What changed between these two versions of this image, and what CVEs did we shed?"

It shells out to three standard supply-chain tools — crane (tags/digests/manifests), cosign (SPDX SBOM attestations), and grype (registry-direct CVE scanning) — wraps them in strict validation, rate limiting, and a digest-keyed cache, and exposes four small tools to any MCP client.

Guardrails (why this exists and what it will not do)

  • Unofficial. Interview-prep project. Not affiliated with, endorsed by, or representing Chainguard in any way.

  • Free-tier public data only. cgr.dev free-tier images, their public Sigstore attestations, and public upstream registries. No gated catalog access, no scraping.

  • Polite to registries. Every registry-touching call goes through a token bucket (4 burst, 1/s refill) and a digest-keyed on-disk cache under .cache/, so repeated queries do not hammer cgr.dev or Docker Hub.

  • Honest about gaps. Missing SBOMs, unresolvable platforms, free-tier tag limitations, and scanner-database freshness are reported in data_gaps fields — never papered over as empty-but-successful results.

Related MCP server: MCP Image Compare Server

The four tools

Tool

Question it answers

list_versions(image)

What tags exist, with digests and created dates — plus how many historical digests are publicly recoverable from signature tags.

diff_packages(image, ref_a, ref_b)

Package-level delta (added / removed / version-changed) between two refs, from the SPDX SBOM attestations attached to each digest.

diff_cves(image, ref_a, ref_b)

CVEs shed and introduced between two refs, from identical registry-direct grype scans.

compare_to_upstream(image, upstream_ref)

The money tool: a Chainguard image vs. its upstream equivalent, CVE counts side by side.

Refs are tags (latest) or digests (sha256:<64 hex>); platform defaults to linux/amd64 (linux/arm64 supported).

Real demo results (run 2026-07-21, grype DB of 2026-07-21)

These are actual outputs from npm run smoke on this machine — no numbers below are invented, and they will drift as images and the vulnerability database update.

1. "What CVEs did we shed?" — cgr.dev/chainguard/node, an older digest vs latest

Older ref sha256:0029ab60fc5a… (created 2023-01-14) → latest sha256:c002402b3552… (created 2026-07-17):

454 CVEs → 2 CVEs
447 CVE instances shed (16 Critical, 127 High, 100 Medium, 15 Low, 189 Unknown)
1 introduced (1 Medium), 1 retained
packages: 189 added, 2 removed, 17 version-changed

The older digest was recovered from public signature tags (sha256-*.sig) — the free tier exposes only rolling tags, but 7,697 historical index digests of node are publicly enumerable and diffable this way.

2. Chainguard vs upstream — cgr.dev/chainguard/nginx vs docker.io/library/nginx:latest

cgr.dev/chainguard/nginx:latest      0 CVEs
docker.io/library/nginx:latest     340 CVEs (21 Critical, 58 High, 86 Medium, 7 Low, 103 Negligible, 65 Unknown)

Same scanner, same database, same platform (linux/amd64), both registry-direct — an apples-to-apples scanner comparison, not an official vendor count.

The 3-minute demo script (inside Claude Code)

  1. "What CVEs did we shed moving chainguard/node from sha256:0029ab60fc5a… to latest?" → clean shed/introduced delta with the package changes behind it.

  2. "Compare chainguard/nginx to docker.io/library/nginx." → the zero-CVE claim reproduced live from public data by a tool an agent can call.

  3. Close: built in a day on hardened template conventions, 144 tests, externally reviewed — and an agent can call this before choosing a base image.

Architecture

Claude Code (stdio, primary)          optional: Streamable HTTP (127.0.0.1, Bearer auth)
        │                                        │
        └────────────► McpServer (4 tools, zod-validated inputs)
                           │
             validate.ts   │  tight regexes for image/tag/digest/platform,
             (before any   │  checked BEFORE anything is spawned
              spawn)       ▼
                       exec.ts ── execFile ONLY, binary allowlist {crane, cosign, grype},
                           │      per-arg character checks, hard timeouts + SIGKILL,
                           │      bounded output buffers
                           ▼
            rate-limit.ts (token bucket) ──► crane / cosign / grype ──► registries
                           │
                       cache.ts — digest-keyed on-disk cache (.cache/):
                       immutable entries for digest-addressed content
                       (manifests, configs, SBOMs), TTL entries for tag
                       lists (30m), tag→digest (15m), grype scans (24h)

Data flow for a diff: tag/digest → index digest (crane digest) → platform image digest (crane manifest) → SBOM (cosign download attestation, predicate https://spdx.dev/Document, with legacy .sbom-attachment fallback) and CVEs (grype registry:image@digest -o json) → pure diff logic (src/diff.ts).

Honest limitations

  • Free tier only sees rolling tags. latest, latest-dev, etc. Versioned tags are a paid feature; "older refs" here are historical digests recovered from public signature tags, which are unordered and must be dated individually.

  • CVE counts are scanner-relative. grype against its daily DB — not Chainguard's advisory feed, not an official count from either vendor. Numbers move as the DB updates; scans are cached up to 24h.

  • Upstream images have no SBOMs. diff_packages works where SBOM attestations exist (Chainguard images); for most upstream images it reports a data gap instead.

  • SBOM parity. Chainguard SBOMs are per-platform in-toto attestations; very old digests may predate them (legacy attachment fallback included, but some gaps remain and are reported as such).

  • Attestations are downloaded, not verified. cosign download attestation fetches; it does not verify signatures against Fulcio/Rekor. A production version would cosign verify-attestation against Chainguard's identity.

  • grype's own registry traffic is not rate-limited by this server's token bucket (it pulls layers internally). The bucket gates how often scans start; the 24h scan cache keeps repeats near zero.

Install & run

Prereqs: Node >= 20 and the three binaries:

brew install crane cosign grype

Build and test:

npm install
npm test          # 144 vitest tests — fully offline, binaries mocked
npm run build
npm run smoke     # the real pipeline against public registries (network!)

Claude Code (stdio — primary mode)

claude mcp add image-delta -- node /absolute/path/to/image-delta-mcp/dist/index.js

or in .mcp.json:

{
  "mcpServers": {
    "image-delta": {
      "command": "node",
      "args": ["/absolute/path/to/image-delta-mcp/dist/index.js"]
    }
  }
}

Optional HTTP mode (hardened, off by default)

IMAGE_DELTA_API_KEY="$(openssl rand -hex 24)" PORT=3900 node dist/index.js --http
# POST /mcp with Authorization: Bearer <key>; binds 127.0.0.1; /healthz unauthenticated

Refuses to start without a key (>= 16 chars). Timing-safe Bearer comparison, per-IP token bucket, 2 MB body cap, stateless transport.

Env: IMAGE_DELTA_CACHE_DIR overrides the cache location (default .cache/ in the package root).

Quality gate

  • 144 vitest tests (validation, exec safety, rate limiting, cache, SBOM and grype parsing, diff logic, all four tools against a mocked registry, MCP end-to-end over an in-memory transport, HTTP auth/limits integration). Tests never touch the network.

  • Integration smoke script (scripts/smoke.mjs) exercises the real pipeline; the demo numbers above are its output.

  • Adversarial security self-review + external Codex review — findings and fixes recorded in SECURITY-REVIEW.md.

F
license - not found
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Related MCP Servers

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/joepangallo/image-delta-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server