Two failure modes show up the moment a system exposes more than a handful of tools to an agent or an API consumer:
- Opaque results. A tool returns a payload with no record of why it is what it is or how much to trust it. The caller — human or agent — can't filter low-confidence answers, can't escalate, and can't audit after the fact. Provenance that wasn't captured at the boundary can't be reconstructed later.
- Surface inflation. "We expose 50 tools" quietly comes to mean "50 things that look like they do something," when in reality some execute server-side work, some are read-only, and some are just guidance a human has to walk. The capability claim drifts away from what the surface actually does, and nobody notices until a caller relies on a tool that doesn't really act.
mcp-tool-provenance makes both problems structural rather than aspirational, with one tiny dependency.
Every result self-describes. stamp(result, toulmin=…, tags=…) attaches a fixed envelope — a Toulmin argument (claim/grounds/warrant/qualifier/rebuttal) and a four-dimension tag block (scope/signal_type/attribution/uncertainty) — non-destructively. Because the wrap preserves the original payload and returns a new dict, retrofitting provenance onto an existing surface is a one-line change at the return boundary, not a migration. Now a caller can route on _toulmin.qualifier, filter on _tags.uncertainty, or query an audit log by _tags.attribution, uniformly, across every tool.
The surface can't inflate. drift_check(registry, classification) partitions the registry into exactly three buckets — real_dispatch (executes + writes), read_only (real reads), native_only (guidance only) — and fails if any exposed tool is unclassified or appears in two buckets. The honest "how many tools actually execute" number is derived (real_dispatch + read_only), never asserted. Wire drift_check into CI and a newly registered tool must be classified before it can ship; the published capability count is forced to stay true.
The decisive design choice is the same in both halves: the contract is fixed, the content is yours. The envelope keys and the three buckets never change; the tag values and the tools are caller-supplied. That is what lets the same primitive govern a fintech tool surface, a healthtech one, and a logistics one without a fork.
Both properties are nearly free on day one and expensive on day three. Stamping costs one line at each return site if you do it as you build; bolting it on later means touching every handler and back-filling reasoning that was never recorded. A drift gate added before the surface grows keeps the capability map honest for free; added after, it surfaces a backlog of unclassified tools and inflated claims that someone now has to reconcile. The smallness — ~250 lines, zero deps — is the point: there's no excuse not to adopt it at the start.
A Toulmin-argument layer (the reasoning vocabulary) and a provenance-tag schema (the four-dimension tags) — both of which this package vendors — and a sign-off ledger so every governed mutation a tool performs carries a maker-checker trail next to its envelope. Together they make a tool surface auditable, trustworthy, and impossible to overstate.