Releases: YuCPbit/mcp-proof
Release list
v0.7.2 — Nothing the report says can be quietly rewritten
The third external review found the same class of hole in two places: things the tool said that its fingerprints didn't cover. Both were reproduced as live attacks before being fixed — and both attacks now live in the test suite.
Report schema v3 — verify covers the whole document
The v2 run_hash was a curated field list; the verdict banner, audit status, summary counters, MSSS table and next steps were all editable without breaking mcp-proof verify — a report edited to claim "99/99 MUST · SHIP-READY" verified as intact. The v3 hash is built by subtraction instead: the whole model minus its two fingerprints and the volatile observation block, so a field added later is fingerprinted by default rather than silently joining the editable set. Stored v2 reports still verify under their frozen recipe with an explicit coverage note; flipping the version field defeats itself; schemas newer than the tool are refused. verify is now described as what it is — an internal-consistency check, not a signature (attestation stays on the roadmap).
Hash-stripping no longer disarms the integrity gate
Deleting a single fixture's contract_sha256 inside a current v4 set used to skip its per-fixture check and the whole set's aggregate fingerprint — zero warnings, tampered baseline replayed as truth. Hash requirements now key off the manifest's schema version: in a v3+ set, a hashless fixture is an integrity ERROR like any other tamper. Baselines that predate contract hashing fail closed with re-record instructions; --allow-legacy-fixtures opts in explicitly (on run and replay).
Integrity failures abort — they don't impersonate drift
replay() raises FixtureIntegrityError before the server is even launched. The missing-manifest "best-effort replay" is gone: with no trusted order, stateful sequences replay wrong and manufacture false drift.
One exit-code taxonomy, CLI-wide
Anything escaping a command is one stable line and exit 2 — never a traceback, and never exit 1 for something that isn't target behaviour (MCP_PROOF_DEBUG=1 re-raises for debugging). replay now agrees with run that a missing baseline is exit 2.
Also in this release: a truth pass over both READMEs (verify wording, MSSS described precisely as a 24-entry control matrix — 23 fully documented controls plus the MCP-DEPLOY-04 placeholder), a corrected report footer, and all four demo reports regenerated with the 0.7.2 engine.
140 tests (21 new adversarial tests in tests/test_v072.py) · 3-OS × 3-Python CI green · full details in CHANGELOG.md
v0.7.1 — The auditor knows when it cannot conclude
Release hardening. v0.7 taught the audit to fail closed on bad data; v0.7.1 teaches it to fail closed on itself. Driven by a second external review (release-management focused) — every claim verified against the code before implementation.
An auditor bug is never a target verdict
_safe now swallows only genuine transport failures; any other exception in check logic surfaces as an AUDIT INCONCLUSIVE outcome instead of being converted into "could not start probe" and charged to the target. Exit codes form a taxonomy: 0 passed · 1 the audit completed and the target failed it · 2 the audit did not complete and proves nothing — in either direction. The JSON model and SARIF carry audit.status; the HTML renders an inconclusive banner that blames nobody.
Baselines fail closed
run --fixtures no longer records a baseline implicitly when none exists — a gate that can silently regenerate its own contract is not a gate. Missing baseline → exit 2 with instructions; --record-if-missing opts in explicitly, and a baseline recorded in the same run labels itself "baseline recorded this run — no historical comparison" instead of posing as a regression verdict.
mcp-proof verify report.json
The JSON model is self-contained, so both fingerprints are recomputable offline; verify exits 1 on any post-audit edit, and a flipped verdict vs. a rewritten evidence string are caught by different fingerprints — the output says which. (v0.7.2 later widened the run fingerprint from a curated field list to the whole document.)
Wording now matches the gate
"Zero security findings" overstated a gate that only blocks FAILs; README (both languages) and the verdict banner now say no blocking security findings. A new evidence-scope card states on the first screen what the report proves and what it never assesses (deployment, source, process, authorization); on a failing report, "Recommended next steps" moves above the detail tables.
Also: README restructured (Validation section, Current/Next/Later roadmap, positioning vs. the official conformance suite), --version, Windows Chrome discovery for --pdf. 119 tests.
Backfilled release notes — condensed from the CHANGELOG entry of the same date.
v0.7.0 — The audit fails closed
Integrity hardening. v0.1–v0.6 proved the tool can do a lot; v0.7 proves it knows when it must not quietly keep going. No new lanes — every change makes an existing verdict harder to fool. Driven by an external code review of v0.6.0 (all findings reproduced) plus our own audit of the same code.
One pagination discipline
A single fail-closed collector (pagination.py) replaces four ad-hoc cursor walkers with three different page ceilings. Every lane now audits every page of tools, resources and prompts — a prompt-injection tool hidden on page 2 is seen exactly like one on page 1. Repeating cursors and mid-walk failures are explicit outcomes, never silent truncation. Prompts gained the pagination check the other surfaces always had (PROMPT-04, MUST). Check counts: 32 modern · 27 legacy · 6 security.
Fixture-set integrity gate
Before anything replays, verify_fixture_set recomputes every contract hash and verifies the manifest fingerprint. A listed fixture missing from disk, a fixture edited after recording, a tampered manifest fingerprint, duplicates, stale unlisted files, or a missing manifest each fail the gate — previously they were silently skipped, alphabetized, or replayed out of order. (v0.7.2 later tightened this further: unverifiable baselines fail closed instead of replaying with a note.)
Fixture schema v4
Every content part recorded in full — v3 collapsed non-text parts to their type, so a completely different image replayed as OK; binary payloads become {sha256, bytes} digests. Sequence-prefixed filenames stop same-tool-same-args overwrites, and the manifest fingerprint is order-sensitive: save→get and get→save are different contracts.
Verdicts that respect machines and evidence
Any value change in structuredContent — or in text that parses as JSON — is at least VALUE and fails the gate; "approved"→"denied" can never pass as COSMETIC. Number comparison uses Decimal. Argument synthesis validates every candidate before calling (skipped_synthesis reported); negative probes prove their baseline valid first. TOOL-06 split into static MUST + dynamic TOOL-08 (unobservable → SKIP with the reason, never a silent pass); TOOL-07 reports a hang as a hang, not as rejection. The security walker resolves $ref/allOf and descends nested schemas — config.shell.command can't hide one level down. MSSS gained the partial status so clean-but-indirect evidence can never claim met.
Two fingerprints (report schema v2)
behavior_sha256 covers check/replay verdicts and protocol facts only — reproducible across machines; run_hash additionally freezes auditor version, launch command and evidence text.
30 new adversarial tests (page-2 violations, cursor loops, every fixture-tampering vector, image-payload drift, float-folding, deep-schema injection) · 107 total · CHANGELOG.md
Backfilled release notes — condensed from the CHANGELOG entry of the same date.
v0.6.0 — Schema-driven negative testing
The audit learns to ask the question schemas only imply: does the server enforce what it declares?
- Two-phase argument synthesis: local
$refresolution,allOfmerging,const, deterministicpatterncandidates,formatseeds, exclusive bounds,multipleOfsnapping,minItems/maxItems— same schema, same args, every run. - Verified negative variants: the valid baseline with exactly one field pushed past a declared constraint. Every candidate is checked with
jsonschemabefore use — a finding can never be a generation artifact. (Boolean type-flips use an int: lax validators coerce "yes"-style strings.) - TOOL-07 (SHOULD, both eras): up to two verified-invalid inputs against up to three side-effect-safe tools; a normal answer earns a WARN quoting the minimal reproducer:
TOOL-07 WARN declared constraints not enforced — echo: minimal invalid input (text=oversized) was answered normally
77 tests · CHANGELOG
v0.5.0 — CI-native outputs + GitHub Action
One report model, every output format a CI could want.
- Versioned JSON report model (
report_schema_version: 1) — the single source of truth every format renders from.run_hashstill covers behaviour only. - JUnit XML (
--junit) and SARIF 2.1.0 (--sarif) — checks and replay verdicts land in any CI dashboard and the GitHub Security tab. - Reusable GitHub Action:
- uses: YuCPbit/mcp-proof@v0.5.0
with:
server-command: python my_server.py
fixtures: fixtures/- Report UI: sticky navigation, per-check anchors (
report.html#SEC-03), All / Attention / Passed filters, collapsible MSSS matrix — vanilla JS, self-contained, print/PDF-safe.
67 tests · CHANGELOG
v0.4.0 — Full-surface conformance + contract diff gate
Every MCP surface, plus the static half of the acceptance gate.
- Capability-aware resources & prompts lanes (both protocol eras): RES-01..04, PROMPT-01..03, CAP-02/03. Surfaces a server does not advertise are skipped, never failed; surfaces it advertises must work. CACHE-01 now validates
ttlMs/cacheScopeon every cacheable result observed. - Contract engine:
mcp-proof inspectfreezes the served surface into a fingerprinted manifest (identical surface ⇒ identicalcontract_sha256);mcp-proof diffclassifies every change as BREAKING / ADDITIVE / METADATA and exits non-zero on breaking ones — optional→required flips, type/enum/constraint tightening, removed output fields and weakenedreadOnlyHint/destructiveHintannotations all count. - Annotations-first call planning: MCP tool annotations outrank the name heuristic in both directions;
mcp-proof planshows the AUTO-CALL / SKIPPED decision and its basis per tool before anything touches production. - Recorder, replayer and plan now paginate
tools/listfully.
With all three surfaces and both eras covered, the README's "any MCP server" claim is back — earned this time.
64 tests · CHANGELOG
v0.3.0 — Dual-era protocol support
The conformance lane now speaks both protocol eras — and the regression lane records through either.
Dual-era protocol support (2026-07-28 + legacy handshake)
- Era auto-detection (
--era auto|modern|legacyonrun,record,replay):server/discoverprobed first, exactly like the official client's auto mode; anything that is not positive modern evidence falls back to the initialize handshake — on a fresh probe, so the audited session is never perturbed by the negotiation itself. - 19 modern-era checks, including three negative probes with teeth: requests without the
_metaenvelope must be rejected (ENV-01), an unsupported protocol version must return-32022with the supported list (VER-01), and a mismatchedMcp-Methodrouting header must return-32020over Streamable HTTP (HTTP-01). PlusresultTypeon every result,ttlMs/cacheScopeon list results,_metaserverInfo identity, deterministic tool order. - Dual-era regression sessions: the pinned 1.x SDK keeps serving handshake-era servers; 2026-07-28 servers get a probe-backed session — the 1.x and 2.x SDKs cannot share a venv (fastmcp pins
mcp<2.0), and the lane only needs two RPCs. - Validated against the official v2 SDK in both directions: the official client adopts mcp-proof's hand-rolled modern test server via
server/discover, and mcp-proof runs all three lanes fully green — including recorded and replayed fixtures — against official v2 SDK servers on both transports (stdio, and Streamable HTTP with SSE responses).
53 tests · CHANGELOG
v0.2.1 — Truth Patch
Truth patch: every README claim is now backed by code, and a fresh install works again.
Fixed
- Fresh installs no longer break:
mcpis pinned>=1.29,<2— the 2.x SDK (2026-07-28 spec era) renames the client-facing fields this package reads (Tool.inputSchema→input_schema), so an unpinned install crashed during recording.httpxis now a declared dependency. - CI can no longer go green without HTTP coverage: transport tests locate the interpreter portably and fail (not skip) in CI when the target server cannot boot.
LIFE-02reports the protocol era honestly: the probe requests2025-11-25— the newest revision the legacy initialize handshake carries — and negotiating it is a PASS. The misleading "not yet migrated to 2026-07-28" verdict is gone (that era is announced viaserver/discover; dual-era probing lands in v0.3).- Capability honesty (
LIFE-03): resources- or prompts-only servers are SKIPped, not failed. - The reproducibility claim is now mathematically true: fixtures split into a hashed contract layer and an unhashed observation layer (timestamp/latency/command). Verified end to end — two independent recordings in different environments produce the identical suite fingerprint, and latency advisories no longer perturb the report fingerprint. Fixture schema v3; v1/v2 fixtures keep replaying.
- Crashed servers leave evidence: exit code + a bounded stderr tail land in the report instead of a bare timeout, and pending requests fail fast when the server process dies.
Changed
- README/architecture wording aligned with what the code proves: fingerprinted (not "signed"), explicit transport & protocol-revision support matrix, three deterministic lanes + planned semantic extension, a Limitations section, live CI badge.
- CI: Ubuntu 3.11/3.12/3.13 + macOS + Windows matrix,
ruffgate, and a fresh-install package job (build wheel → runtime-only install → audit the zero-dependency demo end to end).
41 tests · full changelog in CHANGELOG.md