Ask before you trust.
One GET per verdict. No keys, no accounts. Responses are cache-friendly (s-maxage), rate-limited to 30 scans/min per IP, and every scan result is signed so you can verify it offline.
Building an agent? Start with the one-line agent rule. Machines that fetch this domain read /agents.md.
curl "https://<host>/api/v1/verify?target=https://mcp.context7.com/mcp"
/api/v1/scan?target=&kind=auto|mcp|apiFull scan report: findings with evidence, positives, metadata. The interactive API.
/api/v1/verify?target=&kind=Signed passport: the verdict an agent should consult before calling a tool. ed25519 over canonical JSON.
/api/v1/registryStarter registry, scanned live. Cached 15 min.
/api/v1/badge?target=&kind=&style=grade|flatSVG badge for READMEs and dashboards. style=grade (default) or style=flat. Cached 10 min.
/api/v1/feedPer-node, in-memory view of the newest scans: resets on cold start, and on Vercel each route is its own bundled function, so it only sees scans that hit the same instance. A shared cross-user ledger ships with v1.
/api/v1/og?target=&kind=1200×630 social share card with the verdict stamp, used for trust-card link previews.
/api/v1/pubkeyThe signing public key, PEM-encoded.
/api/v1/canary?tool=Mint a tool-bound canary credential — plant it, detect exfiltration by lookup.
passport is serialized with sorted keys (canonical JSON) and signed with ed25519. Verify like this — no toolproof code required:
import { verify, createPublicKey } from "node:crypto";
const res = await fetch(
`https://<host>/api/v1/verify?target=${encodeURIComponent(target)}`
);
const { passport, signature, alg } = await res.json();
// canonical JSON: keys sorted recursively, then serialized
function canonicalize(value) {
if (Array.isArray(value)) return value.map(canonicalize);
if (value && typeof value === "object") {
const out = {};
for (const k of Object.keys(value).sort()) out[k] = canonicalize(value[k]);
return out;
}
return value;
}
const canonical = JSON.stringify(canonicalize(passport));
const ok = verify(
null,
Buffer.from(canonical),
createPublicKey(process.env.TOOLPROOF_PUBKEY),
Buffer.from(signature, "base64")
);
// alg === "ed25519" && ok → the verdict is authentic and unmodifiedThe sort must be recursive — nested objects like findingCounts and scanner are part of the signed bytes. That's the entire ceremony.
-----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAxS90FXIcrGShrTf5yfJxVa+pX1hUf+tnwgNSjPiEbKg= -----END PUBLIC KEY-----
keyId tpk-1 · algorithm ed25519. Rotate = new keyId.
TP-101Invisible characters that smuggle secret instructions past human eyes — but the AI reads them fine.
TP-102Text telling the AI to ignore its rules, change who it is, or hide things from you.
TP-103The tool mentions web addresses. Where do they lead, and who runs them?
TP-104A live password or API key sitting in plain text.
TP-105The tool can touch your files, system or wallet — more than its job needs.
TP-108Talk of deleting everything, wiping disks, resetting things. One wrong call and data is gone.
TP-205Text that tells the AI to act without asking you first.
TP-107Risky actions happen unless you switch them off — and most people never do.
TP-304The tool wants your keys or passwords directly — which end up in logs.
TP-201Data travels unencrypted. Anyone nearby can read and change it.
TP-202No login needed. Fine for public info — risky for anything private.
TP-203A good sign: the tool refuses strangers.
TP-302The API's own manual describes no sign-in at all.
TP-303The API's documentation advertises an unencrypted server.
TP-206Lists files or feeds from unusual, non-standard sources.
Owners control scanning. Before any probe is sent, Toolproof fetches /.well-known/toolproof.txt from the target's own origin. If the requested path matches a Deny line, the scan ends right there with state opted-out — no probes are sent; that single fetch is the only request Toolproof makes, and the opt-out is recorded as a respected refusal, never a penalty.
# /.well-known/toolproof.txt — lines and keys are case-insensitive # Never scan the admin panel or internal tools: Deny: /admin Deny: /internal # Everything else is fine: Allow: / # Reserved for v1 canary attestations: Canary: 9f2e4d1c7b
Matching is plain path prefix: Deny: /admin also blocks /administrator — any path that starts with the pattern is denied — and / or * blocks the whole site. # starts a comment. Allow and Canary are part of the format today and reserved for opt-in semantics in v1 — only Deny affects scanning.
The signed verify endpoint works from any terminal or CI job — no package, API key or account required:
curl "https://toolproof-scan.vercel.app/api/v1/verify?target=https%3A%2F%2Fmcp.context7.com%2Fmcp"
In CI, parse passport.grade or passport.score from the JSON and fail your own policy check. The response also includes the signature for offline verification.
A pull-request gate in one file.
Save this ready-to-run GitHub Actions workflow as .github/workflows/toolproof.yml, then set the repository variable TOOLPROOF_TARGET to the MCP endpoint or API your project uses. It rejects an unverified target or a score below 70; change MIN_SCORE in the file if your policy needs a different threshold.
CLI enforcement.
The zero-dependency CLI exits non-zero for an unverified target or a score below your threshold. It is published as toolproof-scan.
npx -y toolproof-scan https://mcp.example.com/mcp --fail-under 70
Baselines & signed audit evidence.
For teams: commit a baseline of every MCP server you depend on and let CI re-verify it on every push — signature checked locally (ed25519), grade gated by policy, and any change to a tool's model-visible instructions blocks the merge. Every check appends a JSONL audit line with the full signed passport, replayable by any auditor. Details and plans on the enterprise page.
npx -y toolproof-gate --init https://mcp.example.com/mcp --min-grade B npx -y toolproof-gate --audit audit.jsonl # in CI: exit 1 on drift
Pin a live verdict anywhere. The badge is an SVG that re-scans its target on every cache refresh (~10 min); the embed is a 320×220 card that does the same — a grade that tracks the target over time, not a screenshot.
[](https://toolproof-scan.vercel.app/t?target=mcp.context7.com%2Fmcp)
<iframe src="https://toolproof-scan.vercel.app/embed?target=mcp.context7.com%2Fmcp" width="340" height="240" loading="lazy" title="Toolproof trust card"> </iframe>
style=grade renders the verdict stamp, style=flat a one-line badge. Add &kind=mcp or &kind=api to skip auto-detection.
Watch the text your model reads.
For verified MCP surfaces, every passport carries toolTextHash — a SHA-256 fingerprint of every tool description, prompt, resource and server instruction the model sees. Monitoring is diffing that hash over time; alerts are whatever notifies you (CI email, Slack webhook, a scheduled task).
# GitHub Actions — fails (and emails) when the tool's text changes
name: toolproof-watch
on:
schedule: [{ cron: "0 */6 * * *" }] # every 6 hours
jobs:
watch:
runs-on: ubuntu-latest
steps:
- name: Compare fingerprint
run: |
H=$(curl -s "${{ secrets.TOOLPROOF_URL }}/api/v1/verify?target=${{ secrets.TOOLPROOF_TARGET }}" | jq -r .passport.toolTextHash)
if [ "$H" != "${{ secrets.TOOLPROOF_LAST_HASH }}" ]; then
echo "::error::Tool text changed — fingerprint $H (was ${{ secrets.TOOLPROOF_LAST_HASH }})"
exit 1
fiBrowser-side: pin a tool from its trust card — the watchlist on the leaderboard re-checks your pins every visit.
Trap credentials that identify the thief.
GET /api/v1/canary?tool=your.host mints a unique, tool-bound canary — a decoy secret shaped like a cloud key. Plant it where the tool reads. If the string ever surfaces in an agent transcript, a log, or a paste site, it identifies the tool it was stolen from.
Declare it in your toolproof.txt with Canary: <token> — scanners then report “canary declared by owner” as a positive signal. Real-time beacon alerting (a phone-home tripwire) ships in v1.1.
- · Scans are point-in-time. A passport speaks for its scannedAt, not for the tool's next deploy.
- · Static analysis of tool text — not a sandbox. Behavioral canaries ship in v1.
- · Rate limit 30 scans/min/IP. Please cache; responses carry s-maxage.
- · v0 is best-effort: no SLA, no warranty. A passing grade is a receipt, not a guarantee.