Technical overview · rules atf-6 · 13 October 2026

Is this MCP server safe to install? A signed answer anyone can check.

Agents install MCP servers by name, and the code behind a name can change after it is approved. ATPP scans every server in the official MCP registry every day and publishes, for each one, a signed claim about what its listing declares and what it actually installs. Anyone can verify the claim offline, without trusting our servers, and a company can gate a launch on it.

Intent

Trust attaches to a name. The bytes behind it move.

A team approves io.example/weather once. After that, the client installs whatever that name resolves to: a newer version, a repointed container tag, a package published by a new owner. In our scan of 12 October 2026, 1,111 servers reach a different domain or package owner than when they were first published (a listing swap), 222 listings name a package version that does not exist, and 33 packages were built from a different repository than the one their listing declares.

style="margin-top:18px">A README badge cannot fix this on its own: it is an image fetched from somebody's server, and the server decides what it shows. So the claim does not live in the image. It is a signed file, bound to the exact listing and artifacts that were scanned, that expires, and that any verifier can check against a key it already holds.

  • One claim per mark: a score says "passed N of M published checks on a date", never "safe".
  • Offline-verifiable: the verifier carries our public key; it does not need to ask us.
  • Unbuyable and red only on proof: every public server is scored whether or not its publisher asked, and paying changes nothing.
  • Time-bounded, method published, disputes logged: a claim expires after 7 days; the rules and the dispute log are public. The charter.
How it works

From the registry to a signed, anchored claim

  1. FetchA GitHub Actions job runs daily at 21:20 UTC and downloads every version of every server from registry.modelcontextprotocol.io.
  2. ResolveFor each listed package it records the exact artifact: npm integrity hash, PyPI file sha256s, or the container digest behind the tag, plus the repository named in npm or PyPI Sigstore build provenance.
  3. ScoreRules (atf-6) run against the manifest and the resolved artifacts. For remote servers, the scan also reads the advertised tool list (handshake and tools/list only). No tool is called and nothing is installed or executed.
  4. SignEach score becomes an in-toto statement in a DSSE envelope, signed with Ed25519, and is published to the badge service.
  5. AnchorThe day's manifest (the hash of every claim) is recorded in Sigstore Rekor, committed to a public Git log, and co-signed keyless by a public workflow.

The checks (rules atf-6)

CheckFails whenApplies to
static-secretsThe server authenticates with a long-lived key in an env var or headerAll
secret-no-defaultA field marked secret ships a default valueServers with secret fields
transport-tlsA remote endpoint is plain http://Remote servers
version-pinnedA package is a range or latest, not an exact versionPackages
source-publishedNo source repository is declaredAll
artifact-hashAn MCPB bundle has no content hashMCPB
oci-digestA container image is referenced by a mutable tag, not @sha256:OCI images
target-stableThe current version reaches a different domain or package owner than the first one (listing swap)Servers with history
artifact-resolvesA listed npm or PyPI version was never publishednpm, PyPI
build-provenanceThe Sigstore build record names a repository other than the declared one (a missing record is noted, not failed)npm, PyPI with provenance
tools-stableA remote server's tool definitions changed while its version did not (the rug-pull pattern, OWASP MCP03)Remote servers that list tools without credentials
unchanged-since-last-scanThe manifest changed since the previous scanFrom the second scan

The claim

An in-toto Statement v1 in a DSSE envelope. The subject is mcp:<namespace>/<server> with the sha256 of the exact registry manifest that was scanned, so anyone can re-hash the live listing and compare.

predicate: {
  "version": "0.0.82", "rules_version": "atf-5", "passed": 6, "total": 6,
  "checks":    [["target-stable", true, "points at the same domain and package owner as the first version"], …],
  "artifacts": [["npm", "@playwright/mcp", "0.0.82", "sha512-OCqf…", "microsoft/playwright-mcp"]],
  "tools":     {"sha256": "…", "count": 21},   // remote servers that list tools without credentials
  "as_of": "2026-10-12", "valid_until": "2026-10-19",
  "scope": "static manifest scan — not a runtime claim", "badge_sha256": "…"
}

Keys and transparency

  • Root key atpp-root-2026 is held offline and signs only the key list. The verifier embeds it, so a hijacked domain cannot swap keys.
  • Scan key atpp-scan-2026-10 signs claims. Since 12 October 2026 it lives only as a GitHub Actions secret used by the scan job; it is listed, rotatable and revocable in the signed key list.
  • Daily manifest commits to the hash of every claim issued that day. Its hash is committed to github.com/bl014h/atpp/log and anchored in Sigstore Rekor, so a claim cannot be backdated, swapped or replayed later, even by us.
  • Keyless co-signature: a read-only workflow in the public repository signs each day's manifest with Sigstore keyless signing (GitHub OIDC identity, short-lived certificate, Rekor entry).
  • The web servers hold only public keys. The badge service renders grey unless a signature verifies; it cannot mint a claim.

Where the same claim appears

SurfaceWhat it doesStatus
README badgePinned (two committed files, nothing fetched) or live (follows each scan)live
Verify page/v/mcp/<namespace>/<server>: checks, what it installs, the signed claimlive
ChatGPT and ClaudeThe Redthread connector's check_mcp_server verifies the signature before answeringlive
atpp CLIverify, check-config (with a CycloneDX AIBOM), badge, reportlive
Launch gateatpp gate, hooks for Claude Code and Cursor (atpp hook), the CI action's check-config mode, the ToolHive sidecar's --gatepreview
Org policyAllow-list and pinned digests; exported as a ToolHive permission profilepreview
For developers

Check, pin and gate in a few commands

One dependency (cryptography), one file, no account.

pip install cryptography
curl -sO https://raw.githubusercontent.com/bl014h/atpp/main/atpp.py

python3 atpp.py check-config                       # every MCP server in your Claude, Cursor, VS Code, Windsurf config
python3 atpp.py verify io.github.example/server    # signature, live registry hash, public log, Rekor, expiry
python3 atpp.py gate io.github.example/server --image ghcr.io/example/server@sha256:… --strict   # exit 0 allow, 1 deny

In CI

Fail a pull request that adds an MCP server needing review. Pin the action by commit SHA.

- uses: bl014h/atpp@<commit-sha>
  with:
    mode: check-config
    configs: .mcp.json .cursor/mcp.json .vscode/mcp.json
    fail-on-unscanned: "false"

With ToolHive preview

python3 atpp.py profile io.github.example/server --policy org.json > profile.json
thv run --isolate-network --permission-profile profile.json io.github.example/server
python3 toolhive/atf_receipts.py serve --gate io.github.example/server --gate-policy org.json   # failure_policy: fail

The policy file is yours: {"servers": {"io.github.example/server": {"image": "…@sha256:…", "egress": ["api.github.com"], "read": ["/workspace"]}}}. The gate denies servers not on it and images other than the pinned one, on top of the scan checks.

If you publish a server

  • Pin exact versions, and container images by @sha256: digest.
  • Publish from CI with provenance (npm publish --provenance, PyPI trusted publishing) from the repository your listing declares.
  • Ship no default secrets; prefer short-lived, scoped credentials.
  • Add the badge from your verify page. Disagree with a result? [email protected]; outcomes go to the public dispute log.

Verify the keyless co-signature

pip install sigstore==4.5.0
python -m sigstore verify identity --bundle log/DATE.manifest.json.sigstore.json \
  --cert-identity https://github.com/bl014h/atpp/.github/workflows/keyless.yml@refs/heads/main \
  --cert-oidc-issuer https://token.actions.githubusercontent.com log/DATE.manifest.json
For CISOs

What you can rely on, and what you cannot

Your teams already install third-party MCP servers into agents that hold credentials and data. ATPP gives you an independent, checkable record of what those servers are, without installing anything in your estate.

RiskWhat ATPP gives youStatus
An approved server changes hands (listing swap)target-stable on every claim; !! in check-config; the gate deniesdetect gate
What runs is not what was approvedExact artifact digests in the claim; pin to them; the gate compares the image digestdetect gate
A package was not built from its declared sourcebuild-provenance reads npm and PyPI Sigstore recordslive
Long-lived credentials and default secretsstatic-secrets, secret-no-defaultlive
Shadow MCP use across teamscheck-config in CI on every repository; an allow-list policypreview
A server reaching data it should notYour policy, exported as a ToolHive permission profile and enforced by ToolHive's network isolationpreview
Tool poisoning and rug pulls (OWASP MCP03)tools-stable flags a remote server whose tool definitions change without a new version; the gate and the Claude Code / Cursor hooks deny itdetect gate
Evidence for auditSigned, dated claims and a CycloneDX AIBOM of the MCP servers in use; a public log and Rekor anchors you can citelive

Operating model

  • Cadence: a full scan daily; a claim expires after 7 days and its badge turns grey.
  • Data: only public registry metadata and package registries are read. check-config reads your config locally and never uploads it.
  • Assurance you can check yourself: every claim verifies offline against a pinned key; an hourly canary and a daily red team run against production in public.
  • Disclosure: findings about a specific server are handled under our disclosure policy; public research is aggregate only.
  • Key custody: the root key is offline; the scan key is a GitHub Actions secret, rotatable and revocable through the signed key list.

Questions to ask any vendor in this space, including us

  • Can I verify your verdict without calling your API?
  • Does the verdict name the exact bytes, or only the package name?
  • Is there a public, append-only record that stops you rewriting yesterday's verdict?
  • Who decides what a server may reach: you, or my policy?
Limits

What a claim does not say

A level-1 claim is a static scan of a published listing and the artifacts it names. It does not review the code, it does not say the tool descriptions are honest, and it says nothing about what a server does once it runs. The gate and the policy are a preview of a decision, not a sandbox: they check the listing and the digest before a launch. Runtime behaviour belongs to your gateway and your isolation; level 2 and level 3 attestations come from the company running the agent, not from the publisher.

Source code and key history: github.com/bl014h/atpp. Registry figures: trust.redthreadsec.com/mcp-scan. Report a security issue: security.txt.