TOOLPROOFapi docs · v0.1
quickstart

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"
endpoints
GET/api/v1/scan?target=&kind=auto|mcp|api

Full scan report: findings with evidence, positives, metadata. The interactive API.

GET/api/v1/verify?target=&kind=

Signed passport: the verdict an agent should consult before calling a tool. ed25519 over canonical JSON.

GET/api/v1/registry

Starter registry, scanned live. Cached 15 min.

GET/api/v1/badge?target=&kind=&style=grade|flat

SVG badge for READMEs and dashboards. style=grade (default) or style=flat. Cached 10 min.

GET/api/v1/feed

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

GET/api/v1/og?target=&kind=

1200×630 social share card with the verdict stamp, used for trust-card link previews.

GET/api/v1/pubkey

The signing public key, PEM-encoded.

GET/api/v1/canary?tool=

Mint a tool-bound canary credential — plant it, detect exfiltration by lookup.

the passport

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 unmodified

The sort must be recursive — nested objects like findingCounts and scanner are part of the signed bytes. That's the entire ceremony.

current signing key
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAxS90FXIcrGShrTf5yfJxVa+pX1hUf+tnwgNSjPiEbKg=
-----END PUBLIC KEY-----

keyId tpk-1 · algorithm ed25519. Rotate = new keyId.

rule catalog
TP-101
Hidden characters high · description

Invisible characters that smuggle secret instructions past human eyes — but the AI reads them fine.

TP-102
Hidden instructions critical · description

Text telling the AI to ignore its rules, change who it is, or hide things from you.

TP-103
Phone-home addresses medium · description

The tool mentions web addresses. Where do they lead, and who runs them?

TP-104
Exposed secrets high · description

A live password or API key sitting in plain text.

TP-105
Reaching too far low · description

The tool can touch your files, system or wallet — more than its job needs.

TP-108
Wipe-out language high · description

Talk of deleting everything, wiping disks, resetting things. One wrong call and data is gone.

TP-205
Skips asking permission high · description

Text that tells the AI to act without asking you first.

TP-107
Dangers on by default high · schema

Risky actions happen unless you switch them off — and most people never do.

TP-304
Asks for raw passwords medium · schema

The tool wants your keys or passwords directly — which end up in logs.

TP-201
No encryption critical · transport

Data travels unencrypted. Anyone nearby can read and change it.

TP-202
Anyone can connect info · transport

No login needed. Fine for public info — risky for anything private.

TP-203
Checks who's connecting info · transport

A good sign: the tool refuses strangers.

TP-302
No login mentioned anywhere medium · spec

The API's own manual describes no sign-in at all.

TP-303
Manual points to unsafe address critical · spec

The API's documentation advertises an unencrypted server.

TP-206
Points to odd places medium · spec

Lists files or feeds from unusual, non-standard sources.

the toolproof.txt standard

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.

automation

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
badge & embed

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.

[![toolproof](https://toolproof-scan.vercel.app/api/v1/badge?target=mcp.context7.com%2Fmcp&style=flat)](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.

change monitoring

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
          fi

Browser-side: pin a tool from its trust card — the watchlist on the leaderboard re-checks your pins every visit.

canaries

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.

honest limits
  • · 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.